4

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

5
  • Can you add the versions of the compilers? Commented Oct 15, 2024 at 0:58
  • 1
    k::f is an invalid syntax in case of non-static member functions. Clangs seems to extend this invalidity to static member functions. Hence, decltype(k::f) can't be deduced. May be a bug. Commented Oct 15, 2024 at 0:59
  • 1
    @RobertPrévost There's a link in the post to an online demo that uses both the latest stable releases of each compiler as well as the trunk builds. Commented Oct 15, 2024 at 1:38
  • @3CxEZiVlQ What makes you say that decltype(k::f) can't be deduced? All compilers seem to deduce it to type void(). Commented Oct 15, 2024 at 1:41
  • I talked about non-static member functions. k::f is invalid id-expression in C++ if f is a non-static member function. &k::f is valid. Commented Oct 15, 2024 at 1:49

1 Answer 1

5

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.

Sign up to request clarification or add additional context in comments.

8 Comments

What is the point of keeping the "If E2 refers to a static member function, E1.E2 is an lvalue" bullet?
@Barry As far as I know, it shouldn't matter what we say the value category is. However, we do claim in [basic.lval] that every expression is an lvalue, xvalue, or prvalue.
That DR proposed to have the wording in the standard changed to make it clear that the expression void (*q)() = y.f; should be ill-formed, if that is the intended outcome. This seems to suggest that there was room for deciding against treating such expressions as an error. The implementation divergence could also have been addressed by changing the woring to allow for such expressions. So, what was the rationale for ultimately rejecting this?
@303 I think some were of the opinion that the previous wording already banned this, in which case the change in CWG2725 was merely a clarification.
@BrianBi It does seem somewhat odd that they chose to take this path, because, from a metaprogramming perspective, things can get a bit clumsy now. For example: template<auto K> decltype((K.f)) g(); now has to become: template<auto K> decltype((decltype(K)::f)) g();. I feel that there is some leeway to argue that this change makes things rather more complicated for no real benefit.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.