为什么std :: visit必须具有单个返回类型?

时间:2019-05-08 10:33:37

标签: c++ std c++17

在玩std::variantstd::visit时,出现了以下问题:

考虑以下代码:

using Variant = std::variant<int, float, double>;

auto lambda = [](auto&& variant) {
  std::visit(
    [](auto&& arg) {
      using T = std::decay_t<decltype(arg)>;
      if constexpr (std::is_same_v<T, int>) {
        std::cout << "int\n";
      } else if (std::is_same_v<T, float>) {
        std::cout << "float\n";
      } else {
        std::cout << "double\n";
      }
    },
  variant);
};

它可以正常工作,如以下示例所示:

lambda(Variant(4.5));    // double
lambda(Variant(4.f));    // float
lambda(Variant(4));      // int

然后为什么以下操作失败:

using Variant = std::variant<int, float, double>;

auto lambda = [](auto&& variant) {
  std::visit([](auto&& arg) { return arg; }, variant);
};

auto t = lambda(Variant(4.5));

由于静态断言

static_assert failed due to requirement '__all<is_same_v<int
      (*)(__value_visitor<(lambda at main.cc:25:7)> &&,
      __base<std::__1::__variant_detail::_Trait::_TriviallyAvailable, int, float,
      double> &), float (*)(__value_visitor<(lambda at main.cc:25:7)> &&,
      __base<std::__1::__variant_detail::_Trait::_TriviallyAvailable, int, float,
      double> &)>, is_same_v<int (*)(__value_visitor<(lambda at main.cc:25:7)> &&,
      __base<std::__1::__variant_detail::_Trait::_TriviallyAvailable, int, float,
      double> &), double (*)(__value_visitor<(lambda at main.cc:25:7)> &&,
      __base<std::__1::__variant_detail::_Trait::_TriviallyAvailable, int, float,
      double> &)> >::value' "`std::visit` requires the visitor to have a single
      return type."
如成功示例所示,

std::visit显然可以推论arg的类型。那为什么要要求一个单一的返回类型呢?

编译器为Apple LLVM version 10.0.1 (clang-1001.0.46.4),但gcc version 8.3.0失败并出现类似错误。

2 个答案:

答案 0 :(得分:7)

std::visit的返回类型仅取决于访问者的类型以及传递给它的变量。这就是C ++类型系统的工作原理。

如果您希望std::visit返回一个值,则该值必须已经在编译时具有类型,因为在C ++中所有变量和表达式都具有静态类型。

您在该特定行中传递了Variant(4.5)(因此,“显然访问将返回双精度”)这一事实不允许编译器改变类型系统的规则-{{1} } return type 根本无法根据您传递的变体 value 进行更改,并且不可能仅根据 type 来确定一种返回类型访问者的名称和变体的类型。其他一切都会带来极其奇怪的后果。

This维基百科文章实际上只是用std::visit而不是更复杂的if版本讨论了您所遇到的确切情况/问题:

  

例如,考虑一个包含以下代码的程序:

std::visit
     

即使表达式在运行时始终为true,大多数类型检查器也会将程序拒绝为错误类型,因为静态分析器很难(如果不是不可能)确定else分支不会被执行。


如果您希望返回的类型是“ variish-ish”,则必须坚持使用if <complex test> then <do something> else <signal that there is a type error> 。例如,您仍然可以:

std::variant

推论得出的auto rotateTypes = [](auto&& variant) { return std::visit( [](auto&& arg) -> std::variant<int, float, double> { using T = std::decay_t<decltype(arg)>; if constexpr (std::is_same_v<T, int>) { return float(arg); } else if (std::is_same_v<T, float>) { return double(arg); } else { return int(arg); } }, variant); }; 的返回类型为std::visit-只要您不决定一种类型,就必须留在一个变量中(或在单独的模板实例中)。您不能“欺骗” C ++放弃使用变体的身份访问者进行静态输入。

答案 1 :(得分:4)

尽管每个“实现”都是不同的重载,因此可能具有不同的返回类型,但是在某些时候,您将需要一个公共的访问点,而该公共的访问点将需要一个返回类型,因为选择的变体类型仅在运行时已知。

访问者通常会在visit代码中执行该逻辑;实际上,std::visit的真正目的是为您做所有的魔术,并抽象出运行时类型切换。

否则,您基本上会陷入在呼叫站点重新实现std::visit的麻烦。

很容易想到可以使用模板解决所有问题:毕竟,您已经使用了通用lambda,因此所有这些重载都可以自动实例化,所以为什么不能仅将返回类型“已知”呢?同样,它仅在运行时才知道,所以这对您不利。必须有一些将访问结果传递给呼叫站点的静态方法。