Search code examples
c++c++14language-lawyerauto

Is using explicit return type in one translation unit and deduced return type in another allowed?


My question is similar to this one, but subtly different.

Suppose I have two translation units, exec.cpp and lib.cpp, as followed:

// exec.cpp

int foo();

int main() {
    return foo();
}

and

// lib.cpp

auto foo() {
    return 42;
}

Is it legal to compile and link them together? Or is it ill-formed NDR?

Note: both g++ and clang generate the expected executable (i.e. returns 42) with command <compiler> exec.cpp lib.cpp -o program

Note: Arguably this is a bad practice (as the return type can change if the implementation changes, and breaks the code). But I still would like to know the answer.


Solution

  • All standard references below refers to N4861: March 2020 post-Prague working draft/C++20 DIS..


    From [basic.link]/11 [emphasis mine]:

    After all adjustments of types (during which typedefs are replaced by their definitions), the types specified by all declarations referring to a given variable or function shall be identical, except that declarations for an array object can specify array types that differ by the presence or absence of a major array bound ([dcl.array]). A violation of this rule on type identity does not require a diagnostic.

    [dcl.spec.auto]/3 covers that a placeholder type can appear with a function declarator, and if this declarator does not include a trailing-return-type (as is the case of OP's example)

    [...] Otherwise [no trailing-return-type], the function declarator shall declare a function.

    where

    [...] the return type of the function is deduced from non-discarded return statements, if any, in the body of the function ([stmt.if]).

    [dcl.fct]/1 covers function declarators that do not include a trailing-return-type [emphasis mine, removing opt parts of the grammar that do not apply in this particular example]:

    In a declaration T D where D has the form [...] the type of the declarator-id in D isderived-declarator-type-list function of parameter-type-list returning T” [...]

    Thus, the two declarations

    int f();      // #1
    auto foo() {  // #2
        // [dcl.spec.auto]/3:
        // return type deduced to 'int'
    }
    

    both declare functions where the type of the associated declarator-id in D of these T D declarations is

    derived-declarator-type-list function of parameter-type-list returning T

    where in both cases, T is int:

    • explicitly specified in #1,
    • deduced as per [dcl.spec.auto]/3 in #2.

    Thus, the declarations #1 and #2, after all adjustments of types, have identical (function) types, thus fulfilling [basic.link]/11, and the OP's example is well-formed. Any slight variation of the definition of auto f(), however, could lead to a deduced return type which is not int, in which case [basic.link]/11 is violated, NDR.