17

This is similar to Understanding concepts. Check if a member is static, but that Q&A only asks why it doesn't work, and here I'm asking how to fix it.

Consider the following code:

struct A
{
    static int x;
};

struct B
{
    int x;
};

template <typename T>
concept C = requires
{
    T::x;
};

static_assert(C<A>); // OK, as expected
static_assert(!C<B>); // Error, `C<B> == true` but I want it to be false

This code doesn't compile, because C<B> == true, because T::x is allowed even for nonstatic members in unevaluated contexts.

How do I make this compile? I.e., how can I make a concept that allows static members, but rejects nonstatic members?


&& !requires(T t) {t.x;} doesn't work, because it rejects static members too, because t.x is allowed for them.

&& !requires(T t) {&T::x;} doesn't work also, because for static members it forms a regular nonmember pointer.

4
  • I would have expected [](auto){}(T::x); but whereas it doesn't compile with B, concept doesn't reject it :-/ Demo Commented yesterday
  • @Jarod42 Shouldn't it be template <typename T> concept C = requires { [](auto*){}(&T::x); };? Commented yesterday
  • @康桓瑋: No, then it would be member pointer (or regular pointer) (so both valid). Commented yesterday
  • @Jarod42 auto* will not match member pointers, see compiler-explorer.com/z/zEz49ezhc Commented yesterday

3 Answers 3

15

T::x is a pointer-to-member in the case of non-static members, so it matches both static and non-static members.

You can exclude the pointer-to-member by using

#include <type_traits>

template <typename T>
concept C = requires {
    { T::x };
} && !std::is_member_pointer_v<decltype(&T::x)>;
Sign up to request clarification or add additional context in comments.

8 Comments

Mhm, how did I not think of this. Though sadly this is harder to pull off for overloaded member functions.
I think it' a problem only if you have a static and non-static overload, because std::is_member_pointer_v would return true in that case
No, if you have an overloaded x, decltype(&T::x) is a lookup error.
@Swift-FridayPie If it's overloaded, you won't be able to form a member pointer, unless you cast it to a specific pointer type.
If it's overloaded, then T::x is also invalid, Isn't it?
Such invalid lookups cause the requires clause to fail; if you can decide "when overloaded we want this to be false" for some concept, then I think you are in good shape.
@康桓瑋 For member functions, it's easier to call T::x(...); with the right arguments to choose a specific overload, than to know the full function type to cast the pointer to.
Most of the time, that works. Though let's give x a funky type: struct X { int A::* operator&(); };
13

Taking the address of a static member results in a free function pointer or a T* pointer; taking the address of a non-static member results in a member pointer (B::*). The former can match the pointer type, but the latter cannot. So you can:

template <typename T>
concept C = requires { [](auto*){}(&T::x); };

Demo

1 Comment

Great! But concept C is always false in EDG: gcc.godbolt.org/z/qWMh5zqzf
1

As was mentioned in other questions, B::x is a pointer to member of the type int B::*. Your concept can be based on the fact that such pointer cannot be cast to void* unlike normal pointers:

template <typename T>
concept C = requires { reinterpret_cast<void*>(&T::x); };

It works with all tested compilers: https://gcc.godbolt.org/z/nr9ecb564

2 Comments

Fails on GCC and Clang when the member is a function rather than an object: gcc.godbolt.org/z/5j8Y4h3vs
Thanks, updated the answer to support this case as well.

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.