c++c++11compilationtrailing-return-type

Why was the addition of trailing-return-types necessary in C++11?


I've finally started to read up on and I fail to understand why trailing-return-types are required.

I came across the following example, which is used to highlight the problem:

template<class Lhs, class Rhs>
  decltype(lhs+rhs) adding_func(const Lhs &lhs, const Rhs &rhs) {return lhs + rhs;} 

The example is illegal, because decltype(lhs+rhs) does not work, since the identifiers lhs and rhs are only valid after the parsing phase.

I guess my question is about the timing of decltype type resolution. If I am not mistaken, the keyword decltype is used to determine the type of an expression at compile-time.

I fail to see a downside to having decltype perform type resolution after all parsing is completed (which would work fine for the above example). I believe this would have been a simpler way to solve the problem...

Instead, the C++11 standard provides trailing-return-types:

template<class Lhs, class Rhs>
  auto adding_func(const Lhs &lhs, const Rhs &rhs) -> decltype(lhs+rhs) {return lhs + rhs;}

I have no doubt that I am missing something, since I fail to see the other use of trailing-return-types. Where is the flaw in my reasoning?

The trailing-return-types seem like an overly complex solution to me since having decltype type resolution after parsing the full function body would work just as well?


Solution

  • I fail to see a downside to having decltype perform type resolution after all parsing is completed (which would work fine for the above example).

    The downside is that it's not possible without fundamentally altering the basic foundations of the C++ parsing and processing model.

    In order to do what you suggest, the compiler will have to see the decltype syntax and do some basic lexical analysis of the contents of the syntax. Then, it goes on to parse more of the source file. At some later point (when?), it decides to go, "hey, that stuff I looked at before? I'm going to do all of the parsing work for them now."

    As a general rule, C++ doesn't support looking ahead for the definition of symbols. The basic assumption of the C++ parsing framework is that, if the symbol is not declared before it is used, it is a compiler error.

    Classes can get away with lookahead, but only with respect to their members. This is in part because its quite clear when an id-expression could be referring to a member variable (ie: if it's not referring to an already declared local or global variable in scope). That's not the case here, where we're not sure what exactly the id-expression could be referring to.

    Furthermore, what you suggest creates ambiguities. What does this mean:

    int lhs;
    
    template<class Lhs, class Rhs>
      decltype(lhs+rhs) adding_func(const Lhs &lhs, const Rhs &rhs);
    

    Is the decltype syntax refering to the global lhs variable, or the local lhs function parameter?

    The way we do it now, there's a clear delineation between these two:

    int lhs;
    float rhs;
    
    template<class Lhs, class Rhs>
      decltype(lhs+rhs) adding_func1(const Lhs &lhs, const Rhs &rhs);
    template<class Lhs, class Rhs>
      auto adding_func2(const Lhs &lhs, const Rhs &rhs) -> decltype(lhs+rhs);
    

    adding_func1 refers to the global variables. adding_func2 refers to the function parameter.

    So you can either radically break every C++ compiler on the face of the Earth. Or you can simply late-specify your return type.

    Or you can take the C++14 approach and not bother to state it at all.