为什么必须STD ::访问具有单一返回类型

Why must std::visit have a single return type?

本文关键字:单一 返回类型 访问 STD 为什么      更新时间:2023-10-16

在使用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 << "intn";
      } else if (std::is_same_v<T, float>) {
        std::cout << "floatn";
      } else {
        std::cout << "doublen";
      }
    },
  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失败了。

std::visit的返回类型仅取决于访问者的类型,而变体传递给了它。这仅仅是C 类型系统的工作方式。

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

您通过 Variant(4.5)的事实(显然,访问将返回双重&quot"该行中的double d&quort'不允许编译器弯曲类型系统的规则-std::visit返回 type type 根本无法根据您通过的变体 value 更改,并且不可能仅从访问者的 type <em决定一个返回类型。>变体的类型。其他一切都会带来极其奇怪的后果。

这篇Wikipedia文章实际上基本上讨论了您所面临的确切情况/问题,而不是使用if而不是更精致的std::visit版本:

例如,考虑包含代码的程序:

if <complex test> then <do something> else <signal that there is a type error>

即使表达式在运行时始终评估为true,大多数类型的检查器都会拒绝该程序不易于否决,因为静态分析仪很难(如果不是不可能(确定其他分支不会是拍摄。


如果您希望返回的类型为"变体",则必须坚持使用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的推导返回类型是std::variant<int, float, double>-只要您不决定一种类型,就必须保持在变体中(或在单独的模板实例化中(。您不能Quot;C 在变体上放弃使用身份访问者的静态键入。

,尽管每个"实现"都是不同的超载,因此可能具有不同的返回类型,在某个时候,您需要一个共同的访问点,而通用的访问点将需要一个单个返回类型,因为所选变体类型仅在运行时已知。

与访客的常见约定可以在visit代码中执行该逻辑;实际上,std::visit的目的是为您做所有这些魔术并抽象运行时类型切换。

否则,您基本上将卡住在呼叫的std::visit

很容易想到可以使用模板修复所有这些方法:毕竟,您使用了通用的lambdas,因此所有这些过载都是自主实例的,那么为什么返回类型不能只是"知道"呢?同样,它只有在运行时才知道,所以这对您来说是不好的。必须有一些静态的方式将访问结果传递给呼唤。