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 is “derived-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.