3

我试图绕开我的头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(即此处int和)实现一个double,就像在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()所有类型,然后根据设置方式将它们各自的值接收到bar正确的类型foo中?我知道std::get_if<T>()这在这里可能有用,但理想情况下,除非绝对必要,否则我不想检查每种类型的长 if 语句(这是一个简化的示例,我可能希望在我的std::variant)。

4

1 回答 1

10

错误消息很糟糕,但这里的问题是该变体的所有替代方案在访问者中必须具有相同的返回类型。您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,

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

auto bar = std::visit(AllTypesOperatorOverload(), foo);

如果允许您返回不同的类型,则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);

或者,您可以定义一个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);
于 2021-02-09T22:36:30.530 回答