c++c++11decltypetrailing-return-type

Reason for decltype in trailing return type


I was reading a text about the "new" C++ features and came across decltype and usage of that. I understand the reasoning behind the decltype in the trailing return type of something like

template <typename lhsT, typename rhsT>
auto add(lhsT& lhs, rhsT& rhs) -> decltype(lhs + rhs) {
    return lhs +rhs;
}

Without it, the compiler would not be able to derive the return type of the template function. But why is the syntax the way it is?

Why not use something like

template <typename lhsT, typename rhsT>
decltype(lhs + rhs) add(lhsT& lhs, rhsT& rhs) {
    return lhs +rhs;
}

Would feel more "natural" since the return type is declared where it normally is, although as a result of the two arguments. Is it that this clash with something else or does it cause extra work for the compiler if the syntax was this way that's not worth it?


Solution

  • Note

    Without it, the compiler would not be able to derive the return type of the template function.

    This was later fixed in C++14 and the compiler doesn't need the trailing return type as it can infer the return type from the returned expression. The following works fine in C++14.

    template <typename lhsT, typename rhsT>
    auto add(const lhsT& lhs, const rhsT& rhs) {
        return lhs + rhs;
    }
    

    But why is the syntax the way it is?

    The use of trailing return type is useful when the return type is deduced from the arguments, which always are declared after the return type (one can't refer to something that hasn't been declared yet).

    Also, a trailing return type does appear to be more natural in the sense that functional and mathematical notation uses this kind of declaration. I think most types of notation outside of C-style declarations commonly use trailing return type. E.g.:

    f : X → Y

    In above mathematical notation, f is the function name, X the argument, and Y the returned value.

    Just because the flock of pink sheep reject the gray sheep doesn't make it unnatural.

    In your second example it is not possible for the compiler to infer the return types as of above reason, i.e., that the arguments have not yet been declared. However, inference is possible if using the template parameters and std::declval, e.g.:

    template <typename T, typename U>
    decltype(std::declval<T>() + std::declval<U>()) add(const T& lhs, const U& rhs) {
        return lhs + rhs;
    }
    

    std::declval acts as a lazy instantiation of a type and never evaluates, e.g., calls the constructor etc. It is mostly used when inferring types.