[英]Why can I not retrieve the index of a variant and use that to get its content?
我正在嘗試訪問變體的內容。 我不知道里面有什么,但幸運的是,這個變體確實如此。 所以我想我只會詢問變體它所在的索引,然后使用該索引來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::visit 。
基本上,你不能。
你寫了:
我不知道里面有什么,但謝天謝地,變體確實如此
...但僅在運行時,而不是在編譯時。
這意味着您的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>
? 如果它是“both”,這意味着g
包含一個分支(它不包含),或者有兩個版本的g
(它只是將問題推給它的調用者)。 想想f(T,U,V,W)
——編譯器在哪里停止?
實際上有一個關於 C++ 的 JIT 的提議,它可以通過在調用f
那些附加版本時編譯這些附加版本來允許這樣的事情,但現在還為時過早。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.