w1th0utnam3 w1th0utnam3 - 1 year ago 62
C++ Question

Implementing std::variant converting constructor - or: how to find first overload of all conversions from any T to Ti from parameter pack

In the latest working draft (page 572) of the C++ standard the converting constructor of

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


Remarks: This function shall not participate in overload resolution unless
is_same_v<decay_t<T>, variant>
is false, unless
is_constructible_v<Tj, T>
is true, and unless the expression
FUN ( 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

Answer Source

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.

Building the Overload Set

We define a function object that recursively defines an T operator()(T) const for each 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();
  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();
  void operator()() const;

Performing Overload Resolution

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 = std::result_of_t<overload<Ts...>(T)>;

Within variant<Ts...>, we would use it like this:

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

Some Tests

Alright! Are we done? The following tests pass!

// (1) `variant<string, void> v("abc");` // OK
                   best_match<const char*, std::string, void>>);

// (2) `variant<string, string> w("abc");` // ill-formed
                   best_match<const char*, std::string, std::string>>);

// (3) `variant<string, bool> x("abc");` // OK, but chooses bool
                   best_match<const char*, std::string, bool>>);

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

No viable matches

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

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 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) {
  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&&);

More 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");
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download