Search code examples
c++c++11language-lawyerc++14

Auto deduced return types in forward declaration and mixing with old function syntax


Intro

In C++11 it's fine to declare a function like

auto times2(double num) -> double; // A

and define it like

double times2(double num) { // B
    return num*2;
}

The pair A, B can also be mixed the other way around.

C++14 introduces a third way

auto times2(double num) { // C
    return num;
}

Q

  1. Can style C be mixed with A / B in a declaration/definition pair ?
  2. Can C stand alone as a signature (when the body of the function is not yet provided) ?

    // waiting for the definition to provide info on return type
    auto times2(double); 
    

Solution

  • Mixing the new- and old-school

    7.1.6.4p13 auto specifier [dcl.spec.auto]

    Redeclarations or specializations of a function or function template with a declared return type that uses a placeholder type shall also use that placeholder, not a deduced type.

    The above quotation makes it ill-formed to mix the new with the old.

    More specifically any such mixing introduces an ambiguating since the only difference in the two declarations is the return-type of the function; two declarations where one uses auto and the other a non-deduced type T are, in other words, not referring to the same function.

    auto f ();
    auto f () { return 123; } // legal, return type is `int`
    
    auto g ();
    auto g () -> int;         // ill-formed, an invalid overload of `g` introduced
    
    int  h ();
    auto h ();                // ill-formed, an invalid overload of `h` introduced
    

    Is a function declaration using C++14's return-type deduction equivalent of a forward declaration?

    There are rules related to automatic return-type deduction in C++14 that states that a function which yet hasn't had its return-type deduced is not usable in a context where such is required, that means that (A) below is not equivalent of an old-school declaration (ie. that a function can be used without yet having the definition).

    auto foobar (double); // (A)
    
    int main () {
      auto ret = foobar(3.14f); // ill-formed, return type is not known (yet)
    }
    
    ...
    

    7.1.6.4p9 auto specifier [dcl.spec.auto]

    If the type of an entity with an undeduced placeholder type is needed to determine the type of an expression the program is ill-formed. ...


    Are you saying that a declaration where the return-type is auto is... useless?

    No, far from it, there are cases where a forward declaration certainly is required and contexts where the return-type mustn't be known until a later time, such as inside a template.

    template<class T>
    auto foobar (T);
    
    template<class T>
    auto barbaz (T val) { return foobar (val); }
    
    template<class T>
    auto foobar (T val) { return      val * 2; }
    
    int main () {
      barbaz (1234); // return-type is int
      barbaz (3.1f); // return-tupe is float
    }
    

    Inside barbaz cannot know the return-type of foobar until we have actually instantiated barbaz, but without a forward declaration of auto foobar(T), we couldn't refer to such name inside our template.