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

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>

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);
};

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.


Although each "implementation" is a different overload, and could thus have a different return type, at some point you will need a common point of access and that common point of access will need a single return type, because the selected variant type is only known at runtime.

It is common convention with a visitor to perform that logic inside the visit code; indeed, the very purpose of std::visit is to do all that magic for you and abstract away the runtime type switching.

Otherwise, you would basically be stuck reimplementing std::visit at the callsite.

It's tempting to think that this could all be fixed using templates: after all, you've used generic lambdas so all these overloads are autonomously instantiated, so why can't the return type just be "known"? Again, it's only known at runtime, so that's no good to ya. There must be some static way of delivering the visitation result to the callsite.

Tags:

C++

C++17

Std