std::посещение не может определить тип std::вариант

#c #c 11 #gcc #c 17

Вопрос:

Моя цель-получать любое значение из массива данных без указания типа каждый раз, когда я получаю значение. Я создал специальные таблицы, описывающие информацию о полях (имя и тип поля), а также написал функцию, которая помогает мне правильно интерпретировать данные.

Код представлен ниже:

 #include <iostream>
#include <variant>
#include <assert.h>
#include <string_view>
#include <unordered_map>

enum class ValueType : uint8_t
{
    Undefined       = 0x00,
    Uint32,
    AsciiString,
};

typedef uint64_t FieldId;

struct FieldInfo
{
    std::string _name;
    ValueType _type;
};

typedef std::unordered_map<FieldId, FieldInfo> FieldContainer;

static FieldContainer requestFields =
{
    { 0, { "user-id",   ValueType::Uint32, } },
    { 1, { "group-id",  ValueType::Uint32, } },
};

std::variant<uint8_t, uint32_t, std::string_view> getValue(ValueType type,
                                                           const uint8_t* data,
                                                           size_t length)
{
    if (type == ValueType::Uint32)
    {
        assert(length == sizeof(uint32_t));
        return *reinterpret_cast<const uint32_t*>(data);
    }
    else if (type == ValueType::AsciiString)
    {
        return std::string_view(reinterpret_cast<const char*>(data), length);
    }

    return static_cast<uint8_t>(0);
}


int main(int argc, char *argv[])
{
    const uint8_t arr[] = {0x00, 0x11, 0x22, 0x33};
    size_t length = sizeof(arr);

    const auto value = getValue(ValueType::Uint32, arr, length);

    std::visit([](autoamp;amp; arg)
                    {
                        if ( arg == 0x33221100 )
                        {
                            std::cout << "Value has been found" << std::endl;
                        }
                    }, value);
    return 0;
}
 

Я ожидаю, что компилятор правильно выведет возвращаемое значение и позволит мне сравнить числа. Однако я получил следующие сообщения компилятора:

 error: no match for ‘operator==’ (operand types are ‘const std::basic_string_view<char>’ and ‘int’)
   57 |                         if ( arg == 0x33221100 )
      |                              ~~~~^~~~~~~~~~~~~

error: invalid conversion from ‘int’ to ‘const char*’ [-fpermissive]
   57 |                         if ( arg == 0x33221100 )
      |                                     ^~~~~~~~~~
      |                                     |
      |                                     int
 

Я знаю, что могу получить ценность, позвонив std::get :

     if ( std::get<uint32_t>(value) == 0x33221100 )
    {
        std::cout << "Value has been found" << std::endl;;
    }
 

Но это не то, чего я хочу достичь.

Вопрос в том, могу ли я использовать представленный подход для получения значения без указания типа в каждом месте кода, где мне это нужно?

Информация об окружающей среде:

  • ОС: Linux
  • Компилятор: g (GCC) 11.1.0
  • Стандарт: C 17

Комментарии:

1. arg может быть uint8_t , uint32_t или std::string_view . Тело лямбды должно быть действительным для всех трех. В то arg == 0x33221100 время как не имеет смысла, когда arg имеет тип std::string_view .

2. Как компилятор может знать, какой вариант содержится во время выполнения ?!

3. Похоже , вы ожидаете, что компилятор заглянет внутрь getValue , выполнит полный семантический анализ своей бизнес-логики и сделает вывод, что uint32_t при вызове с помощью он создаст удержание варианта ValueType::Uint32 . Если это так, то вы ожидаете слишком многого.

4. Если вы всегда передаете жестко закодированную константу в качестве первого параметра getValue , то вам было бы лучше просто написать три отдельные функции , такие как getInt8Value getInt32Value и getStringValue , с соответствующими типами возвращаемых значений, и вообще не беспокоиться variant об этом. На самом деле, вы можете написать эти функции так же хорошо, как getValue и просто вызвать их.

5. Ну, это не то, как работает C . Это принятие желаемого за действительное.

Ответ №1:

Одно из фундаментальных правил C , когда речь заходит об оптимизации любого рода, заключается в том, что никакая оптимизация компилятора не может иметь никаких «наблюдаемых эффектов».

Это означает, среди прочего, что хорошо сформированный код не может быть оптимизирован в плохо сформированный код. И плохо сформированный код не может быть оптимизирован в хорошо сформированный код.

 const auto value = getValue(ValueType::Uint32, arr, length);
 

getValue() объявляется как возвращающий a std::variant . Вот каков его возвращаемый тип, и вот к какому value типу выводится. value объявлен std::variant . Полная остановка. Может ли он или не может на самом деле иметь какое-либо конкретное значение, не имеет значения для определения того, правильно ли сформирован код или нет. Последующее std::visit , следовательно, должно быть хорошо сформировано для всех возможных значений вариантов, и вы уже понимаете, что это не для одного из них.

Что ж, это в значительной степени конец истории. Приведенный код неверно сформирован и неверен на языке C . Верно, что в приведенном коде getValue() не возвращается вариант, содержащий значение, для которого последующее std::visit неправильно сформировано. Однако это не имеет значения по изложенным причинам.

Все вышесказанное также означает следующее: если объявленный вариант либо не может удерживать значение которых std::visit плохо образован, или посетитель код корректируется, поэтому он прекрасно образован, то: если компилятор может вывести фактический тип, который возвращается сюда, компилятор может (но не обязан) полностью оптимизировать и не построить тот вариант, в первую очередь, и просто генерировать код, который возвращает единственное возможное значение/значения, а также посещение каждого значения напрямую. Это связано с тем, что устранение конструкции/разрушения варианта не будет иметь заметных эффектов.

Ответ №2:

Вы можете использовать «if constexpr», чтобы обойти проблему, о которой упоминал Игорь Тандетник. Если вы можете использовать c 20, вы можете использовать понятия в сочетании с «if constexpr» для группировки типов и обработки их, чтобы вам не нужно было писать if для каждого типа.

Пример:

 #include <cstdint>
#include <iostream>
#include <type_traits>
#include <variant>
struct StructA
{
};

struct StructWithCoolFunction
{
  void
  coolFunction ()
  {
  }
};

struct AnotherStructWithCoolFunction
{
  void
  coolFunction ()
  {
  }
};

struct UnhandledStruct
{
};

typedef std::variant<StructA, StructWithCoolFunction, AnotherStructWithCoolFunction, UnhandledStruct> MyVariant;
template <typename T> concept HasCoolFunction = requires(T t) { t.coolFunction (); };

auto const handleVariant = [] (auto amp;amp;arg) {
  using ArgType = typename std::remove_cv<typename std::remove_reference<decltype (arg)>::type>::type;
  if constexpr (std::is_same<StructA, ArgType>::value)
    {
      std::cout << "ArgType == StructA" << std::endl;
    }
  else if constexpr (HasCoolFunction<ArgType>)
    {
      std::cout << "some struct with a function 'coolFunction ()'" << std::endl;
    }
  else
    {
      std::cout << "not handled type" << std::endl;
    }
};

int
main ()
{
  auto variantWithStructA = MyVariant{ StructA{} };
  std::visit (handleVariant, variantWithStructA);
  auto variantWithStructWithCoolFunction = MyVariant{ StructWithCoolFunction{} };
  std::visit (handleVariant, variantWithStructWithCoolFunction);
  std::visit (handleVariant, MyVariant{ UnhandledStruct{} });
  return 0;
}
 

Если у вас нет c 20, вы также можете попытаться найти решение с помощью type_traits до c 20, возможно:

 if constexpr(std::is_integral<ArgType>::value) 
 

мог бы тебе помочь.

Комментарии:

1. Коронис, большое спасибо за ваш ответ! Я умею использовать C 20. Скорее всего, это то, что мне было нужно. Я скомпилировал это и разобрал двоичный файл. Я был впечатлен тем, что компилятор обнаруживает все типы и генерирует только 3 вызова std::cout. Я постараюсь применить это решение для своего кода.

2. пожалуйста, отметьте его как ответ, если он отвечает на ваш вопрос.