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_t
、uint32_t
或 std::string_view
。 lambda 的主体必须对所有三个都有效。而当arg
是std::string_view
类型时,arg == 0x33221100
没有意义。
编译器如何知道运行时包含什么变体?!
您似乎期望编译器查看getValue
内部,对其业务逻辑执行完整的语义分析,并推断当使用ValueType::Uint32
调用时,它会产生一个持有uint32_t
的变体。如果是这样,你的期望太高了。
如果您总是将硬编码常量作为getValue
的第一个参数传递,那么您最好只编写三个单独的函数,例如getInt8Value
、getInt32Value
和getStringValue
,具有正确的返回类型,并且根本不用理会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 和 MSVC 调试器的堆栈损坏“重载”结构