实现std::变量转换构造函数-或者:如何从参数包中找到从任何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++标准的最新工作草案(第572页)中,std::variant
的转换构造函数被注释为:
template <class T> constexpr variant(T&& t) noexcept(see below );
设Tj为如下确定的类型:为每个可选类型Ti构建一个虚函数FUN (Ti)。重载解析为表达式
FUN (std::forward<T>(t))
选择的重载FUN (Tj)定义了可选的Tj,它是构造后包含值的类型。Effects:初始化*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
如何模拟假想的过载解析以获得最终类型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");
- 将无符号char*转换为std::istream*C++
- 可以有效地转换 std::any 与 std::any_cast
- 尝试转换 std::chrono::d uration 会导致"rep cannot be a duration"编译错误
- 封送处理 - 转换 std::向量<char>到字符串^ 反之亦然
- 在 C++ 中转换 std::string,使其可以是 C# 中的 byte[]
- 转换 std::vector<T> 为 char *
- 转换std ::绑定到功能指针
- 强制转换 std::vector 的所有成员
- 无法转换'std::string{又名std::basic_string<char>}'到&
- 转换std::string到cv::Mat对象
- 转换std::chrono::system_clock::time_point::min()为字符串时,无效空指针错误
- 简单的模板化函数,用于转换 std::vectors - "illegal use of this type as an expression"
- Const转换std容器
- 如何强制转换std::make_unique,以便可以使用在类中声明的函数
- 如何向下转换std::shared_ptr
- 转换 std::vector<uint8_t> 为 QImage
- 使用std::dynamic_pointer_cast向上转换std::shared_ptr
- 如何转换std::vector<无符号字符>到向量<char>而无需复制
- 正在强制转换std::pair
const&T1 const, T2>const&安全的 - 如何在boost.python中转换std::string*