Чому я не можу отримати індекс варіанту і використовувати його для отримання його вмісту?


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
Вибачте за упущення, я оновив запитання
Алекс

Відповіді:


4

По суті, ви не можете.

Ви написали:

Я не знаю, що там, але, на щастя, варіант є

... але тільки під час виконання, а не під час компіляції.
А це означає, що ваша idxцінність - це не час компіляції.
А це означає, що ви не можете користуватися get<idx>()безпосередньо.

Щось, що ви могли зробити, - це вислів перемикача; потворно, але це спрацювало б:

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, тому що це може бути три різні речі ( 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. Це трохи складніше: ви можете використовувати лямбда / шаблонну функцію, яка є типово-агностичною і працює для всіх типів варіанту, або передати функтор з перевантаженим оператором виклику:

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 .


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

… І варіант конвекспресу не дуже варіант.
Девіс Оселедець

@DavisHerring - Це теж правда.
max66

2

Проблема виникає з примірників шаблонів під час компіляції, тоді як індекс, який ви отримуєте, обчислюється під час виконання. Аналогічно, типи C ++ також визначаються під час компіляції, тому навіть з autoдекларацією вони resповинні мати конкретний тип для добре сформованої програми. Це означає, що навіть без обмеження на шаблон, те, що ви намагаєтеся зробити, по суті неможливо для нестабільного виразу std::variants. Як би можна було обходити це?

По-перше, якщо ваш варіант справді є постійним виразом, код збирається і працює так, як очікувалося

#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містить гілку (якої немає), або що є дві версії g(що просто підштовхує проблему до його абонента). І подумайте - f(T,U,V,W)де зупиняється компілятор?

Насправді є пропозиція щодо JIT для C ++, яка б дозволила подібні речі, склавши ці додаткові версії, fколи вони викликаються, але це дуже рано.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.