return 来自 std::variant 到 std::visit 中可能类型的值

return value from possible type in std::variant through std::visit

我正在努力思考 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 class 中那样通过访问者检索数据,它只提供 operator() 接受 int 作为参数的实现,那行得通.但是,一旦我尝试为 std::variant 中的每种类型实现 operator(),即这里的 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, std::enable_if_t<std::is_same<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(),然后根据 foo 的设置方式将它们各自的值接收到具有正确类型的 bar 中?我知道 std::get_if<T>() 在这里可能有用,但理想情况下,我不想对每种类型进行长 if 语句检查,除非绝对必要(这是一个简化的示例,我可能想要我的 std::variant).

中还有更多类型

错误消息很糟糕,但这里的问题是变体的所有备选方案必须在访问者中具有相同的 return 类型。您的 AllTypesOperatorOverload 不遵守此规则,returning 一个 double 和一个 int,它们不是同一类型。

libstdc++ or any version of libc++ produce much better error messages的最新版本明确告诉你这一点(以下是我绕过的词):

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);

如果您被允许 return 不同的类型,bar 的类型将取决于 foo 在运行时持有哪个替代项。这在 C++ 中行不通。


请注意,使用 lambda 表达式而不是外部定义的结构为 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);

或者,您可以定义一个 overloaded 辅助结构,让您重载 lambda:

template <typename... Lambdas>
struct overloaded : Lambdas...
{
    template <typename... Fns>
    explicit constexpr overloaded(Fns&&... fns)
        : Lambdas(std::forward<Fns>(fns))...
    {}

    using Lambdas::operator()...;
};
template <typename... Lambdas>
overloaded(Lambdas...) -> overloaded<Lambdas...>;

// Usage:
std::visit(overloaded{
    [](int value) {
        std::cout << "visiting int\n";
        std::cout << "bar: " << value << '\n';
    },
    [](double value) {
        std::cout << "visiting double\n";
        std::cout << "bar: " << value << '\n';
    }
}, foo);