我正在尝试访问变体的内容。我不知道里面有什么,但值得庆幸的是,它确实有。因此,我想我只想问问该变体在哪个索引上,然后使用该索引来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
值不是编译时。并且that表示您不能直接使用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
),请参见:
编译器需要在编译时知道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
,[ C0]或float
)。 C ++是一种静态类型的语言,编译器必须能够从随后的表达式中推断出char
的类型(即,它必须始终是同一类型)。
如果您已经知道它将是什么,则可以将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&
// ...
}
。这稍微复杂一点:您可以使用与类型无关的lambda /模板化函数,并且该函数适用于所有变体的类型,或者将函子与重载的调用运算符一起传递给函子:
std::visit
请参见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::visit需要(对于std::get<idx>(var);
)需要一个编译时已知值。
所以idx
值
constexpr
但是要将// VVVVVVVVV
constexpr std::size_t idx = var.index();
初始化为idx
,也必须将constexpr
设为var
constexpr
问题是由于模板是在编译时实例化的,而要获取的索引是在运行时计算的。同样,C ++类型也在编译时定义,因此即使使用// VVVVVVVVV
constexpr std::variant<int, float, char> var { 42.0F };
声明,auto
也必须具有具体的类型,程序才能格式正确。这意味着即使不受模板的限制,对于非恒定表达式res
而言,您试图做的事情本质上也是不可能的。如何解决这个问题?
首先,如果您的变量确实是一个常量表达式,则代码将按预期方式编译和运行
std::variant
否则,您将不得不使用一些手动分支机制
#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);
}
。>>
这在C ++的模型中本质上是不可能的;考虑