4 ms·
It's useful whenever you want a variant with a default state which is different from any value you'd actually put it it, to differentiate between e.g. std:
by jcelerier 2y ago
It's useful whenever you want a variant with a default state which is different from any value you'd actually put it it, to differentiate between e.g.
std::variant<int, whatever> v;
// v is zero
and
std::variant<int, whatever> v = 0;
And don't want to pay the space and time overhead of wrapping in an std::optional which will use a whole other byte at least for not good reason
- glitchc 2y ago> And don't want to pay the space and time overhead of wrapping in an std::optional which will use a whole other byte at least for not good reason This. monostate's behavior should be captured in the std::optional spec. There's no need to create a new type.
- masklinn 2y agoThat is opposite of sense. monostate is a much more general concept, if you want to argue about the need for a new type, the answer is to remove optional because it’s a `variant<T, monostate>`. It doesn’t work the other way around.
- glitchc 2y agoThat's putting the cart before the horse. Languages are designed for developers to use, not compilers to optimize. The intention and use of std::optional is clear. Edit: My point, if not clear, is that the compiler should add the extra bit of code for when an optional is empty automatically, rather than requiring that an optional be defined (it's optional!) unless it is explicitly typed as monostate.
- masklinn 2y agoThat makes even less sense, what "extra bit of code" are you talking about? monostate is not designed to be used with optional: although there's the odd case where that's useful an optional<monostate> is bijective to a boolean, and because C++ does not have zero-sized types it's less efficient (as it takes two bytes).
- ryukoposting 2y agoParton my naiveté, but if you wanted a value that's either a thing, or not-a-thing, why wouldn't you express that with a std::optional? What advantage does std::monostate have versus option types? Brevity, I guess? I suppose the most brief way to express absence or presence of value in C++ is a pointer, but I could be tried at the Hague for that take. None of this is to be facetious, I'm really trying to learn here. I'm not a C++ guy by trade. C and Rust are my day-to-day languages.
- OskarS 2y agoYou're not wrong, if you just want "a T or nothing", optional is the way to go. But what if you want "a T, a U, a V, or nothing"? Then you do std::variant<std::monostate, T, U, V> Or std::optional<std::variant<T, U, V>> But then the "none" state is a bit more "special", it's not just one of the options. The sizeof of the type will also probably be a bit bigger, because it has to contain both the bool for the optional, as well as the tag for the variant. The other obvious use is what the article states, that it allows the variant to be default constructed even if none of its members are. Though you can do that with optional as well. It's mostly a matter of style. I mostly avoid std::monostate because the name is so confusing, I really agree with the other users that something like std::unit or std::none would be better.
- ryukoposting 2y ago> it allows the variant to be default constructed even if none of its members are Ah, okay! So it's serving the same purpose as the "None" in this Rust snippet: enum Foo { None, // <-- impl core::Default and return this T(T), U(U), V(V), } That makes sense, though it really shows how the legacy of C++'s type system limits stdlib design in the present day. It'd be awfully nice to be able to just write std::variant<void, T, U, V>.
- OskarS 2y agoExactly. The difference here is that std::variant doesn’t “name” the options, you just get the type (and an index, so variant can contain duplicate types). So you have to have some marker type that’s like “this contains nothing”. Fundamentally, Rust’s enums are a much better way of doing this thing compared to std::variant, but C++ did the best it could without changing the core language. Which arguably they should have done.
- masklinn 2y agoThat… doesn’t make any sense? The size of an optional<T> is `sizeof T + 1` because it needs a boolean for the set/unset flag. The size of a variant is… the same, at least, because it needs to store a discriminator integer (apparently both gcc and clang do optimise to a byte when there are less than 256 variants). Or do you mean adding a monostate to an existing variant rather than wrap that variant into an `optional`? In which case you should fix your example (and provide a version wrapped in an optional) because they’re very confusing.
- jcelerier 2y agoI don't understand what's confusing with my example aha. Current situation: you have std::variant<int, whatever> x; you now want to discriminate on whenever x has been initialized explicitly or not, the two cases I posted: // case 1 std::variant<int, whatever> x; // case 2 std::variant<int, whatever> x = 0; you have two options: option A: does not needlessly increase sizeof, does not add indirection penalty upon access: std::variant<std::monostate, int, whatever> x; option B: needlessly increases sizeof, adds indirection penalty upon access (mostly relevant when compiling in debug mode without inlining if you still want to have a semblance of performance): std::optional<std::variant<int, whatever>> x;