In the latest working draft (page 572) of the C++ standard the converting constructor of std::variant is annotated with:
template <class T> constexpr variant(T&& t) noexcept(see below );
Let Tj be a type that is determined as follows: build an imaginary function FUN (Ti) for each alternative type Ti. The overload FUN (Tj) selected by overload resolution for the expression
FUN (std::forward<T>(t))defines the alternative Tj which is the type of the contained value after construction.Effects: Initializes *this to hold the alternative type Tj and direct-initializes the contained value as if direct-non-list-initializing it with
std::forward<T>(t).[...]
Remarks: This function shall not participate in overload resolution unless
is_same_v<decay_t<T>, variant>is false, unlessis_constructible_v<Tj, T>is true, and unless the expressionFUN ( std::forward<T>(t))(with FUN being the above-mentioned set of imaginary functions) is well formed.
On 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 mimic the imaginary overload resolution to obtain the final type Tj?
The technique I'll describe is to actually build an overload set, and perform overload resolution by attempting to call it and see what happens with std::result_of.
We define a function object that recursively defines an 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;
};
We can now 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;
Within variant<Ts...>, we would use it like this:
template <typename T, typename U = best_match<T&&, Ts...>>
constexpr variant(T&&);
Alright! Are we done? The following tests pass!
// (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) to pass, actually. Let's explore a few more cases:
If there are no viable matches, the constructor simply SFINAEs out.
We get this behavior for free in best_match, because std::result_of
is SFINAE-friendly as of C++14 :D
We want the best match to be a unique best match. This is (2) that we would like to fail. For example, we can test this by checking that the result of best_match appears 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;
}
We can then augment this condition onto best_match in a 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...>;
(2) now fails, and we can simply use best_match like this:
template <typename T, typename U = best_match<T&&, Ts...>>
constexpr variant(T&&);
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");
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