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

tom*_*tom 3 c++ c++17 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;
}
Run Code Online (Sandbox Code Playgroud)

允许变体保留(在此简化示例中)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, std::enable_if_t<std::is_same<T, double>::value>>
  T operator()(const T &data) {
    std::cout << "visiting double node" << std::endl;
    return data;
  }
};
Run Code Online (Sandbox Code Playgroud)

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

Jus*_*tin 8

错误消息很糟糕,但这里的问题是所有变体的替代品在访问者中必须具有相同的返回类型。您AllTypesOperatorOverload不遵守此规则,返回 adouble和 an int,它们不是同一类型。

最新版本的libstdc++ 或任何版本的 libc++ 会产生更好的错误消息,明确告诉您这一点(以下是我包装的字):

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,
Run Code Online (Sandbox Code Playgroud)

这是有道理的,因为当您查看此行时, 的类型是bar什么?

auto bar = std::visit(AllTypesOperatorOverload(), foo);
Run Code Online (Sandbox Code Playgroud)

如果允许您返回不同的类型,bar的类型将取决于foo运行时保留的替代项。这在 C++ 中是行不通的。


请注意,有更简单的方法可以std::visit使用 lambdas 而不是外部定义的结构来创建访问者。您可以使用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);
Run Code Online (Sandbox Code Playgroud)

或者,您可以定义一个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);
Run Code Online (Sandbox Code Playgroud)