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


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.