What are the name lookup and type simplification rules for trailing return types?

IMO, you have two unrelated questions here, I'll try to answer the first one.
It is covered by [basic.scope.class]/1.5:

The potential scope of a declaration that extends to or past the end of a class definition also extends to the regions defined by its member definitions, even if the members are defined lexically outside the class (this includes static data member definitions, nested class definitions, member function definitions (including the member function body and any portion of the declarator part of such definitions which follows the declarator-id, including a parameter-declaration-clause and any default arguments ([dcl.fct.default]).

In the out-of-class definition of the member function

auto X::f() -> foo { /* ... */ }

the trailing-return-type is following the declarator-id X::f, so it is the potential scope for the class members, so the unqualified lookup finds X::foo when foo is mentioned there.


For #1, see C++17 [basic.lookup.qual]/3:

In a declaration in which the declarator-id is a qualified-id, names used before the qualified-id being declared are looked up in the defining namespace scope; names following the qualified-id are looked up in the scope of the member's class or namespace.

An ordinary leading return type precedes the declarator-id, namely X::f so it is looked up at namespace scope. A trailing return type follows it, so it is looked up in class scope.

For #2, observe that the syntax for trailing-return-type from [dcl.decl]/4 is:

-> type-id

and according to [dcl.fct]/2, that type is the function's return type.

If you were to use a leading return type, the determination of the function's return type would have to be determined recursively by [dcl.fct]/1:

In a declaration T D where D has the form

D1 ( parameter-declaration-clause ) cv-qualifier-seq(opt) ref-qualifier(opt) noexcept-specifier(opt) attribute-specifier-seq(opt)

and the type of the contained declarator-id in the declaration T D1 is “derived-declarator-type-list T”, the type of the declarator-id in D is “derived-declarator-type-list noexcept(opt) function of (parameter-declaration-clause) cv-qualifier-seq(opt) ref-qualifier(opt) returning T”, where ...

Here, T represents a decl-specifier-seq. If you had a typedef-name that denoted int(*)(int), say, FPII, then you could just use that:

FPII g(float);

But if you want to do it the hard way, we have to find T and D1 such that when the derived-declarator-type-list, i.e., the sequence of type transformations D1 would inflict on T according to the syntactic form of D1, are applied to "function of int returning T", the result is "function of float returning pointer to (function of int returning int)".

This will be satisfied if the derived-declarator-type-list is "function of float returning pointer to", and T is int. The declarator D1 must therefore have the syntactic form * declarator-id (float) in order to yield said derived-declarator-type-list. We have to add an extra pair of parentheses in order to get the binding correct in the overall declaration.

There is no "transformation" going on here from the trailing return type to a leading return type. Instead, the trailing return type just lets you specify the return type directly, whereas the leading return type is interpreted by this algorithm of recursively unwrapping the declarator. While this makes sense under the principle of "declaration follows usage", it tends to be a bit difficult for humans to grasp intuitively, including very experienced C++ programmers. And especially so when we have to do it in reverse (write down the declaration, instead of interpreting an existing one).