バリアントのインデックスを取得して、そのコンテンツを取得するために使用できないのはなぜですか?


10

バリアントのコンテンツにアクセスしようとしています。何が入っているのかはわかりませんが、ありがたいことに、バリアントにはあります。だから私はバリアントにそれがどのインデックスにあるのか尋ねて、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();
               ^~~

どうすれば修正できますか?


3
あなたが得ているエラーは、定数式ではないインデックスに関連していると思います。コンパイラのエラーメッセージを投稿して、意味のあるヘルプを提供できるようにしてください。
patatahooligan

constexprがありませんか?
Rlyeh

おっと!エラーについて話しましたが、エラーの正確なテキストを投稿していません。
ジョナサンウッド

1
省略してすみません、質問を更新しました
Alex

回答:


4

基本的にはできません。

あなたが書いた:

何が入っているのかわかりませんが、ありがたいことに、バリアントは

...しかし、コンパイル時ではなく、実行時のみ。
そして、それはあなたのidx価値がコンパイル時ではないことを意味します。
そして、その手段は、あなたが使用することはできませんget<idx>()直接。

あなたができることの1つは、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)については、以下を参照してください。

ランタイム数値テンプレートパラメータをシミュレートするためのイディオム?


@Caleth:はい。編集。
einpoklum

5

コンパイラは、テンプレート引数として使用されているため、が機能するためには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、それは三つの異なるもの(可能性があるため、intfloatまたは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。これは少し複雑です。型にとらわれず、バリアントのすべての型で機能するラムダ/テンプレート関数を使用するか、オーバーロードされた呼び出し演算子でファンクターを渡すことができます。

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 ::訪問詳細と例については、を。


3

問題は、コンパイル時間の既知の値を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 };

…そしてconstexprバリアントはそれほどバリアントではありません。
デイビスヘリング

@DavisHerring-それもまた真実です。
max66

2

この問題は、取得したインデックスが実行時に計算されているときに、コンパイル時にテンプレートがインスタンス化されることから発生します。同様に、C ++型もコンパイル時に定義されるため、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);
}

ビジターパターンを使用してこれらのブランチを定義できます。std :: visitを参照してください。


1

これは、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は、ブランチが含まれている(含まれていない)か、バージョンが2つあるg(問題を呼び出し側にプッシュしているだけ)ことを意味します。そしてf(T,U,V,W)、コンパイラーはどこで停止しますか?

実際には、これらの追加バージョンを呼び出すときにコンパイルすることでこのようなことを可能にするC ++用のJITの提案がありますがf、それは非常に早い段階です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.