>我正在尝试访问变体的内容。我不知道里面有什么,但值得庆幸的是,变体确实如此。所以我想我只会问变体它在什么索引上,然后使用该索引来std::get
它的内容。
但这不会编译:
#include <variant>
int main()
{
std::variant<int, float, char> var { 42.0F };
const std::size_t idx = var.index();
auto res = std::get<idx>(var);
return 0;
}
错误发生在std::get
调用中:
error: no matching function for call to ‘get<idx>(std::variant<int, float, char>&)’
auto res = std::get<idx>(var);
^
In file included from /usr/include/c++/8/variant:37,
from main.cpp:1:
/usr/include/c++/8/utility:216:5: note: candidate: ‘template<long unsigned int _Int, class _Tp1, class _Tp2> constexpr typename std::tuple_element<_Int, std::pair<_Tp1, _Tp2> >::type& std::get(std::pair<_Tp1, _Tp2>&)’
get(std::pair<_Tp1, _Tp2>& __in) noexcept
^~~
/usr/include/c++/8/utility:216:5: note: template argument deduction/substitution failed:
main.cpp:9:31: error: the value of ‘idx’ is not usable in a constant expression
auto res = std::get<idx>(var);
^
main.cpp:7:15: note: ‘std::size_t idx’ is not const
std::size_t idx = var.index();
^~~
我该如何解决这个问题?
编译器需要在编译时知道idx
的值才能std::get<idx>()
工作,因为它被用作模板参数。
第一个选项:如果代码打算在编译时运行,那么将所有内容都constexpr
:
constexpr std::variant<int, float, char> var { 42.0f };
constexpr std::size_t idx = var.index();
constexpr auto res = std::get<idx>(var);
这是有效的std::variant
constexpr
友好(它的构造函数和方法都是constexpr
的(。
第二种选择:如果代码不是在编译时运行的,那么编译器无法在编译时推断出res
的类型,因为它可能是三种不同的东西(int
、float
或char
(。C++是一种静态类型语言,编译器必须能够从后面的表达式中推断出auto res = ...
的类型(即它必须始终是相同的类型(。
您可以使用std::get<T>
,与类型而不是索引一起使用,如果您已经知道它将是什么:
std::variant<int, float, char> var { 42.0f }; // chooses float
auto res = std::get<float>(var);
通常,使用std::holds_alternative
检查变体是否包含每个给定类型,并单独处理它们:
std::variant<int, float, char> var { 42.0f };
if (std::holds_alternative<int>(var)) {
auto int_res = std::get<int>(var); // int&
// ...
} else if (std::holds_alternative<float>(var)) {
auto float_res = std::get<float>(var); // float&
// ...
} else {
auto char_res = std::get<char>(var); // char&
// ...
}
或者,您可以使用std::visit
.这稍微复杂一些:您可以使用与类型无关且适用于所有变体类型的 lambda/模板化函数,或者使用重载调用运算符传递函子:
std::variant<int, float, char> var { 42.0f };
std::size_t idx = var.index();
std::visit([](auto&& val) {
// use val, which may be int&, float& or char&
}, var);
请参阅 std::visita 了解详细信息和示例。
本质上,你不能。
你写道:
我不知道里面有什么,但谢天谢地,变体确实如此
。但仅在运行时,而不是在编译时。
这意味着您的idx
值不是编译时值。
这意味着您不能直接使用get<idx>()
。
你可以做的是有一个switch语句;丑陋,但它会起作用:
switch(idx) {
case 0: { /* code which knows at compile time that idx is 0 */ } break;
case 1: { /* code which knows at compile time that idx is 1 */ } break;
// etc. etc.
}
然而,这是相当丑陋的。正如注释所建议的那样,您不妨std::visit()
(这与上面的代码没有太大区别,除了使用可变参数模板参数而不是显式(并完全避免切换。有关其他基于索引的方法(不特定于std::variant
(,请参阅:
模拟运行时数字模板参数的习语?
问题是std::get<idx>(var);
需要(对于idx
(编译时已知值。
所以constexpr
价值
// VVVVVVVVV
constexpr std::size_t idx = var.index();
但是要初始化idx
constexpr
,var
也必须constexpr
// VVVVVVVVV
constexpr std::variant<int, float, char> var { 42.0F };
问题源于模板在编译时实例化,而您获得的索引是在运行时计算的。同样,C++类型也是在编译时定义的,因此即使使用auto
声明,res
也必须具有具体类型才能使程序格式正确。这意味着即使没有对模板的限制,您尝试执行的操作对于非常量表达式std::variant
s本质上是不可能的。如何解决这个问题?
首先,如果您的变体确实是一个常量表达式,则代码将按预期编译和工作
#include <variant>
int main()
{
constexpr std::variant<int, float, char> var { 42.0f };
constexpr std::size_t idx = var.index();
auto res = std::get<idx>(var);
return 0;
}
否则,您将不得不使用一些手动分支机制
if (idx == 0) {
// Now 'auto' will have a concrete type which I've explicitly used
int value == std::get<0>(var);
}
您可以使用访问者模式定义这些分支,请参阅 std::visit。
这在C++的模型中本质上是不可能的;考虑
template<class T> void f(T);
void g(std::variant<int,double> v) {
auto x=std::get<v.index()>(v);
f(x);
}
哪个f
被称为,f<int>
还是f<double>
? 如果它是"两者",这意味着g
包含一个分支(它没有(,或者有两个版本的g
(它只是将问题推到它的调用者身上(。 想想f(T,U,V,W)
——编译器在哪里停止?
实际上有一个关于 JIT for C++ 的建议,通过在调用时编译这些附加版本的f
来允许这样的事情,但现在还为时过早。