#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 W
s and their constructor overloads. So long as overload resolution selects exactly one such constructor among all possible W
s 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