实现 std::variant 转换构造函数 - 或者:如何从参数包中找到从任何 T 到 Ti 的所有转换的第一个重载
Implementing std::variant converting constructor - or: how to find first overload of all conversions from any T to Ti from parameter pack
在 C++ 标准的最新 working draft(第 572 页)中,std::variant
的转换构造函数注释为:
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, 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.
在 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
如何模仿假想的重载解析以获得最终类型Tj
?
我将描述的技术是实际构建一个重载集,并通过尝试调用它来执行重载解析并查看 std::result_of
.
会发生什么
构建超载集
我们定义一个函数对象,递归地为每个T
定义一个T operator()(T) const
。
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;
};
执行过载解决
我们现在可以使用 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;
在 variant<Ts...>
中,我们会这样使用它:
template <typename T, typename U = best_match<T&&, Ts...>>
constexpr variant(T&&);
一些测试
好的!我们完了吗?以下测试通过!
// (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>>);
嗯,实际上我们不想 (2)
通过。让我们再探讨几个案例:
没有可行的匹配项
如果没有可行的匹配项,构造函数只需将 SFINAE 排除。
我们在 best_match
中免费获得此行为,因为 std::result_of
从 C++14 开始对 SFINAE 友好 :D
唯一匹配
我们希望最佳匹配是唯一的最佳匹配。这是我们希望失败的 (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;
}
然后我们可以以 SFINAE 友好的方式将此条件扩展到 best_match
:
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)
现在失败了,我们可以像这样简单地使用 best_match
:
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");
在 C++ 标准的最新 working draft(第 572 页)中,std::variant
的转换构造函数注释为:
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.
在 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
如何模仿假想的重载解析以获得最终类型Tj
?
我将描述的技术是实际构建一个重载集,并通过尝试调用它来执行重载解析并查看 std::result_of
.
构建超载集
我们定义一个函数对象,递归地为每个T
定义一个T operator()(T) const
。
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;
};
执行过载解决
我们现在可以使用 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;
在 variant<Ts...>
中,我们会这样使用它:
template <typename T, typename U = best_match<T&&, Ts...>>
constexpr variant(T&&);
一些测试
好的!我们完了吗?以下测试通过!
// (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>>);
嗯,实际上我们不想 (2)
通过。让我们再探讨几个案例:
没有可行的匹配项
如果没有可行的匹配项,构造函数只需将 SFINAE 排除。
我们在 best_match
中免费获得此行为,因为 std::result_of
从 C++14 开始对 SFINAE 友好 :D
唯一匹配
我们希望最佳匹配是唯一的最佳匹配。这是我们希望失败的 (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;
}
然后我们可以以 SFINAE 友好的方式将此条件扩展到 best_match
:
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)
现在失败了,我们可以像这样简单地使用 best_match
:
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");