Search code examples
c++language-lawyerc++20

Discrepancy with decltype on static member-function


Clang rejects the following code:

#include <concepts>

struct k { static void f(); };
// clang nope, gcc ok, msvc ok
static_assert(std::same_as<decltype(k::f), decltype(k{}.f)>);

Which seems to boil down to whether decltype(k{}.f) should either be inspected as type void(), type void (&)() or even type void (&&)(). When parenthesizing k{}.f (i.e.: decltype((k{}.f))), both Clang and GCC agree on type void (&)(), while MSVC regards the expression as type void (&&)(). This seems to suggest that both GCC and MSVC do not regard the type-inspection of plain k{}.f as an lvalue-expression.

I presume there is no special exception with decltype when inspecting static member-functions in the form of k{}.f. Therefore, does this mean that both GCC and MSVC are non-conformant?

Demo


Solution

  • These decltype-specifiers are governed by [dcl.type.decltype]/1.3:

    otherwise, if E is an unparenthesized id-expression or an unparenthesized class member access ([expr.ref]), decltype(E) is the type of the entity named by E. If there is no such entity, the program is ill-formed;

    k::f is an unparenthesized id-expression and the function it names has type void().

    What about k{}.f, which is an unparenthesized class member access? This expression is governed by [expr.ref]/7.3:

    If E2 is an overload set, the expression shall be the (possibly-parenthesized) left-hand operand of a member function call ([expr.call]), and function overload resolution ([over.match]) is used to select the function to which E2 refers. The type of E1.E2 is the type of E2 and E1.E2 refers to the function referred to by E2. [...]

    The emphasis is mine. The bolded wording makes the expression k{}.f ill-formed when not used to call the function k::f. Note that even though there is only one function named f found by the lookup, it is still considered an overload set ([basic.lookup.general]/1).

    This kind of expression was made ill-formed by a recent DR (CWG2725). It appears that no compiler has implemented it yet. Possibly the motivation for this core issue was the implementation divergence around the handling of such expressions.

    Before the bolded wording was added, the wording at the end would govern the result: E1.E2 refers to the function referred to by E2, that is, the function k::f, so you would again get void() as the type.