通过 std::visit 从 std::variant 中的可能类型返回值

问题描述

我想把我的头放在 std::variantstd::visit 上,我想提出一种方法来指定我希望我的变量保留的几种类型(这将进入我的 std::variant),然后通过 std::visit 检索存储的数据。考虑以下示例:

#include <iostream>
#include <variant>
#include <string>

struct PrintType {
  void operator()(const int &data) {
    std::cout << "visiting int node" << std::endl;
  }
  void operator()(const double &data) {
    std::cout << "visiting double node" << std::endl;
  }
};

struct SingleOperatorOverload {
  int operator()(const int &data) {
    std::cout << "visiting int node" << std::endl;
    return data;
  }
};

struct AllTypesOperatorOverload {
  int operator()(const int &data) {
    std::cout << "visiting int node" << std::endl;
    return data;
  }
  double operator()(const double &data) {
    std::cout << "visiting double node" << std::endl;
    return data;
  }
};

int main() {

  using var_t = std::variant<int,double>;

  // print int related operator() content,OK
  var_t foo = 42;
  std::visit(PrintType(),foo);

  // print double related operator() content,OK
  foo = 3.1415;
  std::visit(PrintType(),foo);

  // get value and store into bar,struct with single operator(),OK
  foo = 42;
  auto bar = std::visit(SingleOperatorOverload(),foo);
  std::cout << "bar: " << bar << std::endl;

  // get value and store into bar,struct with multiple operator(),ERROR
  auto bar = std::visit(AllTypesOperatorOverload(),foo);
  std::cout << "bar: " << bar << std::endl;

  return 0;
}

允许变体(在此简化示例中)保持 intdouble。如果我只想根据类型打印某些内容(如使用 PrintType 结构所做的那样),那工作正常。

如果我想通过访问者检索数据,就像在 SingleOperatorOverload 类中所做的那样,该类只为 operator() 提供了一个接受 int 作为参数的实现,这是可行的。但是,一旦我尝试为 operator() 中的每种类型实现一个 std::variant,即这里的 intdouble,就像在 AllTypesOperatorOverload 结构中一样,我收到编译错误 error: invalid conversion from '...' {aka double ...} to '...' {aka int ...},所以似乎 std::variant 处理函数签名的方式不同?

我尝试过 SFINAE 但这似乎并没有缓解问题

struct AllTypesOperatorOverload {
  template<typename T,std::enable_if_t<std::is_same<T,int>::value>>
  T operator()(const T &data) {
    std::cout << "visiting int node" << std::endl;
    return data;
  }
  template<typename T,double>::value>>
  T operator()(const T &data) {
    std::cout << "visiting double node" << std::endl;
    return data;
  }
};

现在将报告 error: no type named 'type' in 'struct std::invoke_result<AllTypesOperatorOverload,int&>'。有没有办法为所有类型提供 operator(),然后根据 bar 的设置方式将它们各自的值以正确的类型接收到 foo 中?我知道 std::get_if<T>() 可能在这里有用,但理想情况下,除非绝对必要,否则我不想对每种类型进行长时间的 if 语句检查(这是一个简化示例,我可能想要我的 std::variant 中还有几种类型)。

解决方法

错误消息很糟糕,但这里的问题是变体的所有替代品在访问者中必须具有相同的返回类型。您的 if (strcmp(argv[argc-1],"1234") != 0) 不遵守此规则,返回 AllTypesOperatorOverloaddouble,它们的类型不同。

最新版本的 libstdc++ or any version of libc++ produce much better error messages 明确告诉您这一点(以下是我自己包装的):

int

这是有道理的,因为当您查看此行时,error: static_assert failed due to requirement '__visit_rettypes_match' "std::visit requires the visitor to have the same return type for all alternatives of a variant" static_assert(__visit_rettypes_match, 的类型是什么?

bar

如果允许您返回不同的类型,auto bar = std::visit(AllTypesOperatorOverload(),foo); 的类型将取决于运行时保留的替代 bar。这在 C++ 中是行不通的。


请注意,有更简单的方法可以为使用 lambda 而非外部定义结构的 foo 创建访问者。您可以使用 std::visit:

if constexpr

或者,您可以定义一个 std::visit([](auto value) { if constexpr (std::is_same_v<int,decltype(value)>) { std::cout << "visiting int\n"; } else { static_assert(std::is_same_v<double,decltype(value)>); std::cout << "visiting double\n"; } std::cout << "bar: " << value << '\n'; },foo); 辅助结构,让您重载 lambda:

overloaded

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...