Почему я не могу получить индекс варианта и использовать его для получения его содержимого?


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; некрасиво, но сработало бы

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

... И вариант constexpr не очень вариант.
— Дэвис Херринг

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