std::visit 无法推断 std::variant 的类型

Posted

技术标签:

【中文标题】std::visit 无法推断 std::variant 的类型【英文标题】:std::visit can't deduce type of std::variant 【发布时间】:2021-09-20 12:14:19 【问题描述】:

我的目的是在每次获取值时从数据数组中获取任何值而不指定类型。我创建了描述字段信息(字段名称和类型)的特殊表格,还编写了一个函数来帮助我正确解释数据。

代码如下:

#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([](auto&& 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

【问题讨论】:

arg 可以是 uint8_tuint32_tstd::string_view。 lambda 的主体必须对所有三个都有效。而当argstd::string_view 类型时,arg == 0x33221100 没有意义。 编译器如何知道运行时包含什么变体?! 您似乎期望编译器查看getValue 内部,对其业务逻辑执行完整的语义分析,并推断当使用ValueType::Uint32 调用时,它会产生一个持有uint32_t 的变体。如果是这样,你的期望太高了。 如果您总是将硬编码常量作为getValue 的第一个参数传递,那么您最好只编写三个单独的函数,例如getInt8ValuegetInt32ValuegetStringValue ,具有正确的返回类型,并且根本不用理会variant。事实上,您可以编写这些函数以及 getValue 来简单地调用它们。 嗯,这不是 C++ 的工作方式。这是一厢情愿的想法。 【参考方案1】:

当涉及到任何类型的优化时,C++ 的一个基本规则是任何编译器优化都不能产生任何“可观察到的效果”。

这意味着,除其他外,格式良好的代码无法优化为格式错误的代码。并且格式错误的代码无法优化为格式正确的代码。

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

getValue() 被声明为返回 std::variant。这就是它的返回类型,这就是value 的类型被推导出来的。 value 是声明的 std::variant。句号。对于确定代码是否格式正确而言,它是否可以实际具有任何特定值并不重要。因此,后续的std::visit 必须针对所有可能的变体值形成良好的格式,并且您已经明白它不适用于其中之一。

嗯,这就是故事的结尾。显示的代码格式错误且不是有效的 C++。确实,在显示的代码中,getValue() 不会返回一个变量,该变量包含一个值,而随后的 std::visit 是格式错误的。然而,由于上述原因,这并不重要。

以上所有内容还意味着以下内容:如果声明的变体不能包含 std::visit 格式错误的值,或者访问者的代码已调整为格式正确,则:如果编译器可以推导出这里返回的实际类型,编译器可能(但不是必须)完全优化掉而不是首先构造变体,而只是生成返回唯一可能的值/值的代码,并直接访问每个值.这是因为消除变体的构造/破坏不会产生明显的影响。

【讨论】:

【参考方案2】:

您可以使用“if constexpr”来解决 Igor Tandetnik 提到的问题。如果您可以使用 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 &&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 pre c++20 寻找解决方案:

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

可以帮助你。

【讨论】:

Koronis,非常感谢您的回答!我能够使用 C++20。这很可能是我需要的。我编译了它并反汇编了二进制文件。令我印象深刻的是编译器检测到所有类型并且只生成了 3 个 std::cout 调用。我将尝试为我的代码应用该解决方案。 如果回答了您的问题,请将其标记为答案。

以上是关于std::visit 无法推断 std::variant 的类型的主要内容,如果未能解决你的问题,请参考以下文章

可以优化 std::visit 吗?

std::visit 和 std::variant 用法

std::visit 和 MSVC 调试器的堆栈损坏“重载”结构

C++17 std::variant 比动态多态性慢?

使用偏函数应用程序或 curry 与重载和 std::visit 结合使用时理解错误

是否可以在没有 lambda 的情况下使用 std::visit (只是一个简单的类)? [关闭]