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

dte*_*ell 5 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);
};
Run Code Online (Sandbox Code Playgroud)

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

lambda(Variant(4.5));    // double
lambda(Variant(4.f));    // float
lambda(Variant(4));      // int
Run Code Online (Sandbox Code Playgroud)

那么为什么以下失败:

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

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

auto t = lambda(Variant(4.5));
Run Code Online (Sandbox Code Playgroud)

由于静态断言

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

std::visitarg如成功的例子所示,可以明显地推断出类型。那么,为什么要求具有单一返回类型?

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

Max*_*hof 7

The return type of std::visit depends only on the types of the visitor and the variant passed to it. That's simply how the C++ type system works.

If you want std::visit to return a value, that value needs to have a type at compile-time already, because all variables and expressions have a static type in C++.

The fact that you pass a Variant(4.5) (so "clearly the visit would return a double") in that particular line doesn't allow the compiler to bend the rules of the type system - the std::visit return type simply cannot change based on the variant value that you pass, and it's impossible to decide on exactly one return type only from the type of the visitor and the type of the variant. Everything else would have extremely weird consequences.

This wikipedia article actually discusses basically the exact situation/question you have, just with an if instead of the more elaborate std::visit version:

For example, consider a program containing the code:

if <complex test> then <do something> else <signal that there is a type error>
Run Code Online (Sandbox Code Playgroud)

Even if the expression always evaluates to true at run-time, most type checkers will reject the program as ill-typed, because it is difficult (if not impossible) for a static analyzer to determine that the else branch will not be taken.


If you want the returned type to be "variant-ish", you have to stick with std::variant. For example, you could still do:

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

The deduced return type of std::visit then is std::variant<int, float, double> - as long as you don't decide on one type, you must stay within a variant (or within separate template instantiations). You cannot "trick" C++ into giving up static typing with an identity-visitor on a variant.