Mengapa saya tidak dapat mengambil indeks varian dan menggunakannya untuk mendapatkan kontennya?

10

Saya mencoba mengakses konten varian. Saya tidak tahu apa yang ada di sana, tapi untungnya varian tidak. Jadi saya pikir saya akan bertanya pada varian apa indeksnya dan kemudian gunakan indeks itu untuk std::getisinya.

Tetapi ini tidak mengkompilasi:

#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;
}

Kesalahan terjadi dalam std::getpanggilan:

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();
               ^~~

Bagaimana saya bisa memperbaikinya?

Alex
sumber
3
Saya menduga kesalahan yang Anda dapatkan terkait dengan indeks yang tidak menjadi ekspresi konstan. Silakan kirim pesan kesalahan kompiler sehingga kami dapat memberikan bantuan yang berarti.
patatahooligan
Hilang constexpr?
Rlyeh
Aduh! Anda berbicara tentang kesalahan, tetapi Anda tidak memposting teks kesalahan yang sebenarnya.
Jonathan Wood
1
Maaf atas kelalaiannya, saya memperbarui pertanyaan
Alex

Jawaban:

4

Pada dasarnya, Anda tidak bisa.

Kau menulis:

Saya tidak tahu apa yang ada di sana, tapi untungnya varian tidak

... tetapi hanya pada saat dijalankan, bukan pada waktu kompilasi.
Dan itu berarti idxnilai Anda bukan waktu kompilasi.
Dan itu berarti Anda tidak dapat menggunakan get<idx>()secara langsung.

Sesuatu yang bisa Anda lakukan adalah memiliki pernyataan switch; jelek, tapi itu akan berhasil:

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.
}

Namun ini agak jelek. Seperti komentar menyarankan, Anda mungkin juga std::visit()(yang tidak jauh berbeda dari kode di atas, kecuali menggunakan argumen templat variadic bukannya eksplisit ini) dan menghindari beralih sama sekali. Untuk pendekatan berbasis indeks lainnya (tidak khusus untuk std::variant), lihat:

Idiom untuk mensimulasikan parameter templat angka waktu-berjalan?

einpoklum
sumber
@Caleth: Ya. Diedit.
einpoklum
5

Kompilator perlu mengetahui nilai idxpada waktu kompilasi untuk std::get<idx>()bekerja, karena sedang digunakan sebagai argumen templat.

Opsi pertama: Jika kode dimaksudkan untuk dijalankan pada waktu kompilasi, maka buat semuanya constexpr:

constexpr std::variant<int, float, char> var { 42.0f };

constexpr std::size_t idx = var.index();

constexpr auto res = std::get<idx>(var);

Ini bekerja karena std::variantadalah constexprramah (konstruktor dan metode semua constexpr).

Opsi kedua: Jika kode tidak dimaksudkan untuk berjalan pada waktu kompilasi, yang kemungkinan terjadi, kompiler tidak dapat menyimpulkan pada waktu kompilasi jenis res, karena itu bisa tiga hal yang berbeda ( int, floatatau char). C ++ adalah bahasa yang diketik secara statis, dan kompilator harus dapat menyimpulkan tipe dari auto res = ...ekspresi yang mengikuti (artinya harus selalu tipe yang sama).

Anda dapat menggunakan std::get<T>, dengan jenis alih-alih indeks, jika Anda sudah tahu apa itu:

std::variant<int, float, char> var { 42.0f }; // chooses float

auto res = std::get<float>(var);

Secara umum, gunakan std::holds_alternativeuntuk memeriksa apakah varian memegang masing-masing jenis yang diberikan, dan menanganinya secara terpisah:

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&
    // ...
}

Atau Anda bisa menggunakan std::visit. Ini sedikit lebih rumit: Anda dapat menggunakan fungsi lambda / templated yang bertipe agnostik dan berfungsi untuk semua jenis varian, atau melewatkan functor dengan operator panggilan yang kelebihan beban:

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);

Lihat std :: kunjungi untuk detail dan contoh.

elbrunovsky
sumber
3

Masalahnya adalah yang std::get<idx>(var);membutuhkan (untuk idx) nilai waktu kompilasi diketahui.

Jadi suatu constexprnilai

// VVVVVVVVV
   constexpr std::size_t idx = var.index();

Tetapi untuk menginisialisasi idxsebagai constexpr, juga varharusconstexpr

// VVVVVVVVV
   constexpr std::variant<int, float, char> var { 42.0F };
maks66
sumber
... Dan varian constexpr tidak terlalu varian.
Davis Herring
@ Davidviser - Itu juga benar.
maksimal 66
2

Masalah muncul dari templat yang sedang dipakai pada waktu kompilasi sementara indeks yang Anda dapatkan dihitung pada waktu berjalan. Demikian pula, tipe C ++ juga didefinisikan pada waktu kompilasi sehingga bahkan dengan autodeklarasi, resharus memiliki tipe konkret agar program dapat terbentuk dengan baik. Ini berarti bahwa bahkan tanpa batasan pada templat, apa yang Anda coba lakukan pada dasarnya tidak mungkin untuk ekspresi non-konstan std::variants. Bagaimana orang mengatasi ini?

Pertama, jika varian Anda adalah ekspresi konstan, kode tersebut mengkompilasi dan berfungsi seperti yang diharapkan

#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;
}

Kalau tidak, Anda harus menggunakan beberapa mekanisme percabangan manual

if (idx == 0) {
    // Now 'auto' will have a concrete type which I've explicitly used
    int value == std::get<0>(var);
}

Anda bisa mendefinisikan cabang-cabang ini menggunakan pola pengunjung, lihat std :: visit .

patatahooligan
sumber
1

Ini secara inheren tidak mungkin dalam model C ++; mempertimbangkan

template<class T> void f(T);
void g(std::variant<int,double> v) {
  auto x=std::get<v.index()>(v);
  f(x);
}

Yang fdipanggil, f<int>atau f<double>? Jika "keduanya", itu berarti yang gberisi cabang (yang tidak), atau ada dua versi g(yang hanya mendorong masalah ke peneleponnya). Dan pikirkan f(T,U,V,W)— di mana kompiler berhenti?

Sebenarnya ada proposal untuk JIT untuk C ++ yang akan memungkinkan hal-hal seperti ini dengan mengkompilasi versi tambahan fketika mereka dipanggil, tetapi masih sangat dini.

Davis Herring
sumber