Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is getting the decltype of a deduced member function inside the trailing return type of another member function well-formed?

Whoever is saying that this question is a duplicate of the question "Is there a specific reason why a trailing-return-type is not a complete-class context of a class?" does not know what he/she is talking about. The fact that a trailing return type is not a complete-class context of a class does not explain why the code in this question doesn't compile, although it explains the rejection of the code given in the answer to the other question, specially the part of the code involving the member functions qux and baz, as explained by the OP.

In order to clarify my argument that the code below is valid, you have to take into consideration the second note in [expr.prim.this] which says: In a trailing-return-type, the class being defined is not required to be complete for purposes of class member access. Class members declared later are not visible. As foo is declared before bar in my example, there is nothing to prevent the compiler from accessing foo in the trailing-return-type of bar.

Note that the comment below by @NathanOliver is based on the conjecture that the inline definition for the member function foo below is just syntactic sugar. This needs to be proven from a quote in the Standard. I haven't found that yet. Once that quote is produced, I will certainly accept an answer arguing that the code doesn't compile because a trailing-return-type is not a complete-class context of a class.

struct Test {
        auto foo() {}                       
        auto bar() -> decltype(foo()) {}
    };

prog.cc:3:32: error: use of 'auto Test::foo()' before deduction of 'auto'
    3 |     auto bar() -> decltype(foo()) {}
      |                                ^
prog.cc:3:32: error: use of 'auto Test::foo()' before deduction of 'auto'

[dcl.spec.auto]/9:

If a function with a declared return type that uses a placeholder type has no non-discarded return statements, the return type is deduced as though from a return statement with no operand at the closing brace of the function body. [ Example:

auto f() { } // OK, return type is void

auto* g() { } // error, cannot deduce auto* from void()

— end example ]

[dcl.type.auto.deduct]/(2.1):

A type T containing a placeholder type, and a corresponding initializer e, are determined as follows:

(2.1) for a non-discarded return statement that occurs in a function declared with a return type that contains a placeholder type, T is the declared return type and e is the operand of the return statement. If the return statement has no operand, then e is void();

(2.2) for a variable declared with a type that contains a placeholder type, T is the declared type of the variable and e is the initializer. If the initialization is direct-list-initialization, the initializer shall be a braced-init-list containing only a single assignment-expression and e is the assignment-expression;

(2.3) for a non-type template parameter declared with a type that contains a placeholder type, T is the declared type of the non-type template parameter and e is the corresponding template argument.

According to [dcl.spec.auto]/9 and [dcl.type.auto.deduct]/(2.1) the code should compile. But GCC and cland reject it. What am I missing?

like image 708
Ayrosa Avatar asked Feb 20 '19 21:02

Ayrosa


People also ask

What is Decltype function?

The decltype type specifier yields the type of a specified expression. The decltype type specifier, together with the auto keyword, is useful primarily to developers who write template libraries. Use auto and decltype to declare a template function whose return type depends on the types of its template arguments.

What is trailing return type in C++?

The trailing return type feature removes a C++ limitation where the return type of a function template cannot be generalized if the return type depends on the types of the function arguments.


1 Answers

struct Test
{
    auto foo() { /*1*/ }                       
    auto bar() -> decltype(foo()) {}
};

At marker 1, the name Test::bar is in scope along with all other members of struct Test. Therefore the compiler cannot analyze the body of foo() until the class is complete.

We then have a partial ordering:

  • Parse body of Test::foo() before deducing its return type
  • Complete class Test before parsing body of Test::foo()
  • Analyze trailing return type of Test::bar() before completing class Test (from the question you pled is not a dupe)

and by transitivity, the return type of Test::bar() must be analyzed without yet performing return type deduction for Test::foo().


Since a Standard quote was requested, here it is from [class.mem]:

A class is considered a completely-defined object type (or complete type) at the closing } of the class-specifier. Within the class member-specification, the class is regarded as complete within function bodies, default arguments, noexcept-specifiers, and default member initializers (including such things in nested classes). Otherwise it is regarded as incomplete within its own class member-specification.

like image 54
Ben Voigt Avatar answered Oct 16 '22 10:10

Ben Voigt