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

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

Как я могу это исправить?

Alex
источник
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), см.

Идиома для моделирования параметров числового шаблона во время выполнения?

einpoklum
источник
@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 для подробностей и примеров.

elbrunovsky
источник
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 };
max66
источник
... И вариант 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 .

patatahooligan
источник
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когда они вызываются, но это очень рано.

Дэвис Херринг
источник