Implementation of the conversion constructor std :: variant - or: how to find the first overload of all transformations from any T-Ti from the parameter package

In the last working draft (p. 572) of the C ++ standard, the std::variant conversion constructor is annotated with:

 template <class T> constexpr variant(T&& t) noexcept(see below ); 

Let Tj be a type that is defined as follows: construct an imaginary function FUN (Ti) for each alternative type Ti. The FUN (Tj) overload, selected by overload resolution for the FUN (std::forward<T>(t)) expression FUN (std::forward<T>(t)) , defines an alternative to Tj, which is the type of value contained after construction.

Effects: Initializes * this to hold the alternative Tj type and direct-initializes the contained value, as if direct-non-list-initializing with std::forward<T>(t) .

[...]

Notes: This function should not participate in overload resolution if is_same_v<decay_t<T>, variant> not false, unless is_constructible_v<Tj, T> true, and if the expression FUN ( std::forward<T>(t)) (with FUN is the aforementioned set of imaginary functions).

In cppreference , the following example is used to illustrate the conversion:

 variant<string> v("abc"); // OK variant<string, string> w("abc"); // ill-formed, can't select the alternative to convert to variant<string, bool> x("abc"); // OK, but chooses bool 

How can you reproduce the imaginary overload resolution to get the final Tj type?

+6
source share
1 answer

The technique I will describe is to actually create a set of overloads and execute overload resolution, trying to call it and see what happens to std::result_of .

Build Overload Set

We define a function object that recursively defines T operator()(T) const for each T

 template <typename T> struct identity { using type = T; }; template <typename... Ts> struct overload; template <> struct overload<> { void operator()() const; }; template <typename T, typename... Ts> struct overload<T, Ts...> : overload<Ts...> { using overload<Ts...>::operator(); identity<T> operator()(T) const; }; // void is a valid variant alternative, but "T operator()(T)" is ill-formed // when T is void template <typename... Ts> struct overload<void, Ts...> : overload<Ts...> { using overload<Ts...>::operator(); identity<void> operator()() const; }; 

Overload resolution execution

Now we can use std::result_of_t to simulate overload resolution and find the winner.

 // Find the best match out of `Ts...` with `T` as the argument. template <typename T, typename... Ts> using best_match = typename std::result_of_t<overload<Ts...>(T)>::type; 

Inside the variant<Ts...> we will use it as follows:

 template <typename T, typename U = best_match<T&&, Ts...>> constexpr variant(T&&); 

Some tests

Good! We all? Pass the following tests!

 // (1) `variant<string, void> v("abc");` // OK static_assert( std::is_same_v<std::string, best_match<const char*, std::string, void>>); // (2) `variant<string, string> w("abc");` // ill-formed static_assert( std::is_same_v<std::string, best_match<const char*, std::string, std::string>>); // (3) `variant<string, bool> x("abc");` // OK, but chooses bool static_assert( std::is_same_v<bool, best_match<const char*, std::string, bool>>); 

Well, we don’t want (2) pass, actually. Let's look at a few more cases:

No viable matches

If there are no viable matches, the constructor simply excludes SFINAE. We get this behavior for free in best_match , because std::result_of is SFINAE-compatible with C ++ 14: D

Unique match

We want the best match to be a unique best match. This is (2) that we would like to fail. For example, we can verify this by checking that the result of best_match displayed exactly once in Ts...

 template <typename T, typename... Ts> constexpr size_t count() { size_t result = 0; constexpr bool matches[] = {std::is_same_v<T, Ts>...}; for (bool match : matches) { if (match) { ++result; } } return result; } 

Then we can extend this condition to best_match using the SFINAE-friendly way:

 template <typename T, typename... Ts> using best_match_impl = std::enable_if_t<(count<T, Ts...>() == 1), T>; template <typename T, typename... Ts> using best_match = best_match_impl<std::result_of_t<overload<Ts...>(T)>, Ts...>; 

Conclusion

(2) now fails, and we can simply use best_match as follows:

 template <typename T, typename U = best_match<T&&, Ts...>> constexpr variant(T&&); 

Additional tests

 template <typename> print; // undefined template <typename... Ts> class variant { template <typename T, typename U = best_match<T&&, Ts...>> constexpr variant(T&&) { print<U>{}; // trigger implicit instantiation of undefined template error. } }; // error: implicit instantiation of undefined template // 'print<std::__1::basic_string<char> >' variant<std::string> v("abc"); // error: no matching constructor for initialization of // 'variant<std::string, std::string>' variant<std::string, std::string> w("abc"); // error: implicit instantiation of undefined template 'print<bool>' variant<std::string, bool> x("abc"); 
+7
source

All Articles