Does using concepts allow using decltype on member function

328 Views Asked by At

I came to know that for a class X with a member function named func, the c++ standard doesn't allow us to write decltype(X::func). Thus I expected the below given program to produce an error saying something like invalid use of non-static member function. But to my surprise, the program compiles with all 3 compilers.

#include <iostream>
#include <type_traits> 
#include <concepts>

template < typename T >
concept test = std::same_as <decltype(T::func), int(int) >;

struct D
{
    int func(int);
};

int main()
{ 
    std::cout << test<D>;      
}    

The above program is compiled by all 3 compilers. By looking at [expr.prim.id] it seems the program should be ill-formed and a diagnostic should be emitted. But none of the compilers provide any error. So, does the use of concept in a way shown in the program make the program well-formed or is it still ill-formed and are all compilers wrong?

2

There are 2 best solutions below

0
user12002570 On BEST ANSWER

Does the use of concept in a way shown in the program make the program well-formed

The program is well-formed as explained below. From [temp.constr.normal]:

The normal form of an expression E is a constraint (13.5.2) that is defined as follows:

  • The normal form of a concept-id C<A1, A2, ..., An> is the normal form of the constraint-expression of C, after substituting A1, A2, ..., An for C’s respective template parameters in the parameter mappings in each atomic constraint. If any such substitution results in an invalid type or expression, the program is ill-formed; no diagnostic is required.

(emphasis mine)

Note the emphasis on the last sentence. In particular, it is about parameter mapping. That is, if the parameter mapping results in an invalid type, then only the program will be IFNDR.

But in your given example, the parameter mapping is T ->decltype(T::func) which is not in itself an invalid type. Thus, the above quoted reference is not satisfied.


Now, from [temp.constr.atomic#3]:

To determine if an atomic constraint is satisfied, the parameter mapping and template arguments are first substituted into its expression. If substitution results in an invalid type or expression, the constraint is not satisfied. Otherwise, the lvalue-to-rvalue conversion is performed if necessary, and E shall be a constant expression of type bool. The constraint is satisfied if and only if evaluation of E results in true. If, at different points in the program, the satisfaction result is different for identical atomic constraints and template arguments, the program is ill-formed, no diagnostic required.

(emphasis mine)

And since the substitution of the template argument D results in an invalid construct decltype(D::func), the given constraint is not satisfied(meaning it is false).


are all compilers wrong?

Thus, all compilers are correct in accepting the program and outputting 0.

0
duck On

Substitution failure during satisfaction checking of constraints does not result in the program being ill-formed; it simply causes the constraint to not be satisfied. The relevant text from the standard here is [temp.constr.atomic]/3:

To determine if an atomic constraint is satisfied, the parameter mapping and template arguments are first substituted into its expression. If substitution results in an invalid type or expression, the constraint is not satisfied. Otherwise...

Per [temp.names]/9, to determine the value of test<D> we first need to reduce test's constraint expression to its normal form. For the sake of convenience, let's assume same_as<T, U> is defined as std::is_same_v<T, U> (in actuality it's slightly different, but that doesn't affect this answer).

Normalizing the constraint expression of test results in an atomic constraint std::is_same_v<T, U> with the mapping [T -> decltype(T::func), U -> int(int)]. Note that the parameter mappings denote perfectly valid types (one dependent), so [temp.constr.normal]/1.4 (the IFNDR case) does not apply.

An invalid expression is only formed when D is substituted for (test's) T, which according to the text quoted above simply means that test<D> is false, and the program is well-formed.