#include <optional>
#include <variant>
#include <utility>
#include <set>
int main()
{
std::optional<std::variant<std::pair<int, int>, std::set<int>>> foo(std::in_place, {1, 4});
}
std::variant does not have a constructor that takes std::initializer_list<T> as its first argument, but all that this constructor overload of std::optional does is pass the initializer list and forward other arguments to the internal type constructor?std::variant hold now, std::set or std::pair?std::optional contains the object within itself, depending on where it is stored (stack/data/heap) std::optional makes a copy of the contained object. Monadic functions will be added in C++23 to improve the abstraction in our code by removing the needs of writing boilerplate code.
It holds one of several alternatives in a type-safe way. No extra memory allocation is needed. The variant needs the size of the max of the sizes of the alternatives, plus some little extra space for knowing the currently active value. By default, it initializes with the default value of the first alternative.
What's more, std::optional doesn't need to allocate any memory on the free store. std::optional is a part of C++ vocabulary types along with std::any , std::variant and std::string_view .
When you call the function and pass an instance of T, an optional will be constructed which will own its own copy of T, therefore it will call T's copy constructor. In order to be able to call foo and pass a T without creating a copy, you can declare foo as receiving an optional reference, i.e.
It's a bit complicated, so bear with me.
optional<T> has a constructor that takes an in_place_t and an initializer_list<U> (and optional extra arguments). It will then attempt to construct T as if by this statement:
T t(list, ...);
Where list is the initailizer list and ... are any extra arguments.
T is of course a variant<pair, set>. variant is implicitly convertible from any type V through a template constructor. However, this constructor only exists if V is a type such that, given a value v of this type, the following is true:
W w(std::forward<V>(v));
Where W (I'm running out of letters here) is one of the types in the variant. Now variant can have multiple types, so variant basically considers all possible Ws and their constructor overloads. So long as overload resolution selects exactly one such constructor among all possible Ws in the variant, the conversion will work.
pair has no constructor that takes an initializer_list, so none of its constructors count. set<X> has a constructor that takes an initializer_list<X>.
So, back to the original statement. Because optional had an initializer_list<Y> parameter where Y is deduced, the braced-init-list will deduce Y as int. And int is the same type as the set in the variant. So set<int> can be constructed from an initailizer_list<int>.
And therefore, that's what gets created in the variant.
If you had a vector<int> as part of the variant, you'd get a compile error due to the ambiguity of which to call.
And there's not really a way to fix that through in_place gymnastics. You can't do (std::in_place, std::in_place_type<std::set>, {1, 4}), because the braced-init-list won't get properly deduced. So you'd have to do (std::in_place, std::set{1, 4}) and let move construction move from the temporary into the variant.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With