实现std :: variant转换构造函数 - 或者:如何从参数包中找到从任何T到Ti的所有转换的第一次重载

w1t*_*am3 7 c++ sfinae c++17

在C++标准的最新工作草案(第572页)中,转换构造函数的std::variant注释为:

template <class T> constexpr variant(T&& t) noexcept(see below );
Run Code Online (Sandbox Code Playgroud)

令Tj为如下确定的类型:为每个替代类型Ti构建虚函数FUN(Ti).由表达式的重载决策选择的重载FUN(Tj)FUN (std::forward<T>(t))定义了替代Tj,它是构造后包含的值的类型.

效果:初始化*this以保存替代类型Tj并直接初始化包含的值,就像直接非列表初始化它一样std::forward<T>(t).

[...]

备注:该函数不应参与重载决策,除非is_same_v<decay_t<T>, variant>为假,除非 is_constructible_v<Tj, T>为真,除非表达式FUN ( std::forward<T>(t))(FUN是上述虚函数集)形成良好.

cppreference上,以下示例用于说明转换:

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
Run Code Online (Sandbox Code Playgroud)

你如何模仿假想的重载决策来获得最终类型Tj

mpa*_*ark 8

我将描述的技术是实际构建一个重载集,并通过尝试调用它来执行重载解析,看看会发生什么std::result_of.

构建重载集

我们定义一个函数对象,递归地T operator()(T) const为每个对象定义一个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;
};
Run Code Online (Sandbox Code Playgroud)

执行过载分辨率

我们现在可以std::result_of_t用来模拟重载分辨率,并找到胜利者.

// 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;
Run Code Online (Sandbox Code Playgroud)

在内部variant<Ts...>,我们会像这样使用它:

template <typename T, typename U = best_match<T&&, Ts...>>
constexpr variant(T&&);
Run Code Online (Sandbox Code Playgroud)

一些测试

好的!我们完了吗?以下测试通过!

// (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>>);
Run Code Online (Sandbox Code Playgroud)

好吧(2),实际上我们不想通过.让我们探讨一些案例:

没有可行的匹配

如果没有可行的匹配,则构造函数只是SFINAE.我们免费获得这种行为best_match,因为std::result_of 从C++ 14:D开始,它是SFINAE友好的

独特的比赛

我们希望最佳匹配成为独特的最佳匹配.这是(2)我们想要失败的.例如,我们可以通过检查结果是否best_match恰好出现一次来测试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;
}
Run Code Online (Sandbox Code Playgroud)

然后我们可以best_match以SFINAE友好的方式增加这种情况:

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...>;
Run Code Online (Sandbox Code Playgroud)

结论

(2)现在失败了,我们可以这样简单地使用best_match:

template <typename T, typename U = best_match<T&&, Ts...>>
constexpr variant(T&&);
Run Code Online (Sandbox Code Playgroud)

更多测试

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");
Run Code Online (Sandbox Code Playgroud)

  • `void`是`variant`的有效替代,但是`T operator()(T);`当`T`是'void`时``格式错误.简单的解决方法是专门化`overload <void,Ts ...>`(我已应用).此外,成员函数声明中的`const`限定符是无关紧要的 - "result_of_t <overload <Ts ...>(T)>`是调用非`constst`xvalue`overload <Ts ...>的结果`.(但它们并不正确,所以我把它们留在原地.) (4认同)
  • @Casey:据我所知,如果`T_j`变成一个数组类型而`T`就是一个指针,那么构造函数将被`is_constructible <T_j,T>`条件的SFINAEd输出. (2认同)