Reflecting mirrors

Query properties of a class

After writing how to destructure a function, I’ve been asked if it is possible to do something similar for a struct.

The short answer is that it is not possible.

There is currently no mechanism for getting a set with all member variables, but it is possible to ask and answer specific questions, like "Does this class have a specific function"?

In this notes, I’ll describe how to query

  • typedefs

  • member variables

  • member functions

  • free functions

All approaches use the same underlying technique (SFINAE) and can be used since C++11, eventually with some minor adjustments.

Query there is a given typedef

Why would it be relevant to verify if a type is defined?

An example is provided by transparent comparators.

The details have already been posted, the TL;DR is

#include <type_traits>

template <typename T, typename = void>
struct has_typedef_foo : std::false_type{};

template <typename T>
struct has_typedef_foo<T, std::void_t<typename T::foo>> : std::true_type {};


struct bar1{
    bool foo;
};
struct bar2{
    bool foo();
};
struct bar3{
    using foo = int;
};
struct bar4{
    using foo = void;
};
struct incomplete;
struct bar5{
    using foo = incomplete;
};
struct bar6{
    struct foo{};
};

static_assert(not has_typedef_foo<bar1>{});
static_assert(not has_typedef_foo<bar2>{});
static_assert(    has_typedef_foo<bar3>{});
static_assert(    has_typedef_foo<bar4>{});
static_assert(    has_typedef_foo<bar5>{});
static_assert(    has_typedef_foo<bar6>{});

Obviously, it is hard to reuse, so here is a macro to automate the definitions:

#include <type_traits>

#define DECLARE_TYPEDEF_CHECKER(U) \
    template <typename T, typename = void> \
    struct has_typedef_##U : std::false_type {}; \
    template <typename T> \
    struct has_typedef_##U<T, std::void_t<typename T::U>> : std::true_type {}


// usage:
DECLARE_TYPEDEF_CHECKER(foo);

struct bar{
    using foo = void;
};
static_assert(has_typedef_foo<bar>{});

Query if there is a given member variable

The details are all here, the TL;DR is

#include <type_traits>

template <typename T, typename = void>
struct has_member_foo : std::false_type{};

template <typename T>
struct has_member_foo<T, decltype(void(T::foo))> : std::true_type {};

struct bar1{
    bool foo;
};
struct bar2{
    bool foo();
};
struct bar3{
    using foo = int;
};
struct bar4{
    using foo = void;
};
struct incomplete;
struct bar5{
    using foo = incomplete;
};
struct bar6{
    struct foo{};
};

static_assert(    has_member_foo<bar1>{});
static_assert(not has_member_foo<bar2>{});
static_assert(not has_member_foo<bar3>{});
static_assert(not has_member_foo<bar4>{});
static_assert(not has_member_foo<bar5>{});
static_assert(not has_member_foo<bar6>{});

Again, a macro to make it more usable

#include <type_traits>

#define DECLARE_MEMBER_CHECKER(U) \
    template <typename T, typename = void> \
    struct has_member_##U : std::false_type {}; \
    template <typename T> \
    struct has_member_##U<T, decltype(void(T::U))> : std::true_type {}


// usage:
DECLARE_MEMBER_CHECKER(foo);

struct bar{
    bool foo;
};

static_assert(has_member_foo<bar>{});

Query if there is a given member variable with a given type

For determining if a member variable of a given type exists, it is just sufficient to remove the cast to void, and specify the desired type instead of void.

The macro should also take an additional parameter for disambiguating the different tests; for example

#include <type_traits>

#define DECLARE_TYPEDMEMBER_CHECKER(U, S, ...) \
    template <typename T, typename = __VA_ARGS__> \
    struct has_typedmember_##U##S : std::false_type {}; \
    template <typename T> \
    struct has_typedmember_##U##S<T, decltype(T::U)> : std::true_type {}


// usage:
DECLARE_TYPEDMEMBER_CHECKER(foo, _bool, bool);

struct bar1{
    bool foo;
};
struct bar2{
    int foo;
};
struct bar3{
    int foo;
};

static_assert(    has_typedmember_foo_bool<bar1>{});
static_assert(not has_typedmember_foo_bool<bar2>{});
static_assert(not has_typedmember_foo_bool<bar3>{});

Query if there is a given member function

I did not write previously how to detect member functions, but it should work similarly to member variables.

Except that one should take the address of it, as there are no "member function" types, but only "member function pointer" types.

If that would not be the case, then decltype(void(T::U)) would work for member functions too, and has_member_foo would not distinguish a member variable from a member function.

But using decltype(void(&T::U)) is not sufficient; it works for member functions and member variables!

While one could ignore this, and let the user detect if foo is a member variable or function, it makes sense to provide a way to tell directly if foo is a function or not, as normally the use cases are very different.

With std::is_member_function_pointer, it is possible to determine if a type is a member function or not, and with std::conditional define one type or another

#include <type_traits>

template <typename T, typename = void>
struct has_functionmember_foo : std::false_type{};

template <typename T>
struct has_functionmember_foo<T, decltype(void(&T::foo))> :
 std::conditional_t<std::is_member_function_pointer_v<decltype(&T::foo)>, std::true_type, std::false_type>
{};

struct bar1{
    bool foo;
};
struct bar2{
    bool foo();
};
struct bar2_2{
    bool foo();
    bool foo(int);
};
struct bar3{
    using foo = int;
};
struct bar4{
    using foo = void;
};
struct incomplete;
struct bar5{
    using foo = incomplete;
};
struct bar6{
    struct foo{};
};

static_assert(not has_functionmember_foo<bar1>{});
static_assert(    has_functionmember_foo<bar2>{});
static_assert(not has_functionmember_foo<bar3>{});
static_assert(not has_functionmember_foo<bar4>{});
static_assert(not has_functionmember_foo<bar5>{});
static_assert(not has_functionmember_foo<bar6>{});

Again, a macro for a reusable solution

#include <type_traits>

#define DECLARE_FUNCTIONMEMBER_CHECKER(U) \
    template <typename T, typename = void> \
    struct has_functionmember_##U : std::false_type {}; \
    template <typename T> \
    struct has_functionmember_##U<T, decltype(void(&T::U))> : \
    std::conditional_t<std::is_member_function_pointer_v<decltype(&T::U)>, std::true_type, std::false_type> {}


// usage:
DECLARE_FUNCTIONMEMBER_CHECKER(foo);


struct bar{
    bool foo();
};
static_assert(has_functionmember_foo<bar>{});

The proposed implementation has a limitation: it does not work if there are overloads.

The main reason is that

struct bar{
    bool foo();
    bool foo() const;
};
void baz(){
  &bar::foo;
}

fails to compile with something similar to "a reference to overloaded function could not be resolved", even if we are not even interested in the exact function, as long as there is at least one.

It is possible to disambiguate between the overloads if we know the signature, but in that case, it would not make much sense to ask if there is a member function, as we already know at least one of its signatures.

Thus it does not seem to be possible to write a routine that also works in this constellation, but(!) it is possible to ask if a member function matches a given signature.

Query if there is a given member function with a given signature

#include <type_traits>

template <typename T, typename = void>
struct has_functionmember_foo : std::false_type{};
template <typename T>
struct has_functionmember_foo<T, decltype(void(static_cast<bool(T::*)()>(&T::foo)))> : std::true_type {};

struct bar1{
    bool foo;
};
struct bar2{
    bool foo();
    bool foo(int); // !
};
struct bar3{
    using foo = int;
};
struct bar4{
    using foo = void;
};
struct incomplete;
struct bar5{
    using foo = incomplete;
};
struct bar6{
    struct foo{};
};


static_assert(not has_functionmember_foo<bar1>{});
static_assert(    has_functionmember_foo<bar2>{});
static_assert(not has_functionmember_foo<bar3>{});
static_assert(not has_functionmember_foo<bar4>{});
static_assert(not has_functionmember_foo<bar5>{});
static_assert(not has_functionmember_foo<bar6>{});

And packaged together as a macro:

#include <type_traits>

#define DECLARE_FUNCTIONMEMBER_CHECKER(U) \
    template <typename T, typename = void> \
    struct has_functionmember_##U : std::false_type {}; \
    template <typename T>\
    struct has_functionmember_##U<T, decltype(void(&T::U))> : \
    std::conditional_t<std::is_member_function_pointer_v<decltype(&T::U)>, std::true_type, std::false_type> {}

#define DECLARE_TYPEDFUNCTIONMEMBER_CHECKER(U, N, ...) \
    template <typename T, typename = void> \
    struct has_typedfunctionmember_##U##_##N : std::false_type {}; \
    template <typename T> \
    struct has_typedfunctionmember_##U##_##N<T, decltype(static_cast<__VA_ARGS__>(&T::U), void([]{static_assert(std::is_member_function_pointer<__VA_ARGS__>{}, #__VA_ARGS__ " is not a signature of a member function for T");}))> : std::true_type {}



// usage
DECLARE_FUNCTIONMEMBER_CHECKER(foo);
DECLARE_TYPEDFUNCTIONMEMBER_CHECKER(foo,1, bool(T::*)(int));
DECLARE_TYPEDFUNCTIONMEMBER_CHECKER(foo,2, bool(T::*)());

struct bar1{
    bool foo();
};
static_assert(    has_functionmember_foo<bar1>{});


struct bar2{
    bool foo();
    bool foo(int);
};
static_assert(    has_typedfunctionmember_foo_1<bar2>{});
static_assert(    has_typedfunctionmember_foo_2<bar2>{});
static_assert(not has_typedfunctionmember_foo_1<bar1>{});
static_assert(    has_typedfunctionmember_foo_2<bar1>{});

static_assert(not has_functionmember_foo<bar2>{}, "unfortunately fails, no knwown implementation that would return true");

The static_assert, as used in DECLARE_TYPEDFUNCTIONMEMBER_CHECKER, only works since C++20.

It is an additional verification that the type passed to the macro is a signature of a member function pointer of T. By looking at the macro it is not clear what type one needs to write. It might make sense that int is not a valid parameter, it is not a valid member function signature after all. For this reason alone, some sort of compile-check would be necessary, otherwise has_typedfunctionmember_foo_1 would verify the existence of a member variable.

Unfortunately, the check is not sufficient, as bool(bar1::*)(int), which is a valid member function pointer signature, is also wrong. If used, then has_typedfunctionmember_foo1 would work correctly only for bar1, but would always return false for other types, no matter if they have a member function or not.

Also the signature bool(S::*)(int) is wrong, in that case, has_typedfunctionmember_foo_1 would always deliver the answer false, for any type, unless there is a class named S.

The "correct" signature to use as a parameter is bool(T::*)(int) (with T as struct type), because T is used internally in the macro, and is the one that makes has_typedfunctionmember_foo1 work correctly with all types.

Thus, by destructuring a function, one could write the check as static_assert(std::is_same<std::remove_cvref_t<typename function_traits<VA_ARGS>::owner>, T>{}, "");

Also note that DECLARE_TYPEDFUNCTIONMEMBER_CHECKER has an additional parameter for creating different names for structures, otherwise it would only be possible to verify one specific function signature per function name per translation unit.

Query if there is a given free function

Just like the case of member functions, one might want to know if there is a callable free function.

Even if strictly speaking a free function is not a property of a class, a function still defines the interface of a class: how the class can be used.

Since a free function might not take the desired structure as a parameter at all, one is more interested if a free function with certain criteria exists.

Thus, given a structure s we are not interested in a free function foo exists, but if there is a function foo that takes s as a parameter.

A possible implementation could look like

#include <type_traits>

template <class T, class = void>
struct exists_function_foo : std::false_type {};
template <class T>
struct exists_function_foo<T, decltype(void(foo(std::declval<T>(), 1)))> : std::true_type {};

struct s{};

void foo(s, int);
static_assert(exists_function_foo<s>{});

Note that this check also works for void foo(const s&, int); void foo(s, int, int i = 42);, bool foo(s, int) and void foo(s, double), thus we are not completely hard-coding the function signature, even if it fails for void foo(s&);.

For supporting also the case with s&, it is sufficient to change std::declval<T>() to std::declval<T&>().

Note that this code also does not have issues with function overloads.

Given that there are so many ways to specify a subset of an interface of a function (function with how many parameters? with T taken at which position), I’m not sure what a reusable macro could look like, especially if one wants to double-check that T appears somewhere.

Query if there is a given member function with a not fully specified signature

#include <type_traits>

template <class T, class = void>
struct exists_memberfunction_foo : std::false_type {};
template <class T>
struct exists_memberfunction_foo<T, decltype(void(std::declval<T>().foo()))> : std::true_type {};

struct s{
    int foo() const;
    void foo(int);
};

static_assert(exists_memberfunction_foo<s>{});

Similar to the examples with free functions, the function signature does not need to match exactly; the return type can be different, there can be additional defaulted parameters,

Again, given the endless possibilities of querying if a function with a not fully specified signature exists, I’m not sure how it can be encapsulated meaningfully behind a macro, or some other reusable component, that also asserts that T appears at one meaningful position, like DECLARE_TYPEDFUNCTIONMEMBER_CHECKER does.

From the standard library

The standard library already provides since C++11 a set of tools for querying properties at compile-time, for example, is_enum, is_function, is_reference, is_base_of, is_convertible, conditional, and many others, most of them described at the meta page of cppreference.

Also, since C++20 concepts, are available.

Use-cases

With this set of macros, it is possible to query multiple structural properties of a struct/class at compile time and act accordingly.

The examples where to detect member variables are out of date if you are using at least C++17, in this case, thanks to if constexpr, it is possible to write code that is easier to read and maintain; for example

#include <type_traits>

#define DECLARE_MEMBER_CHECKER(U) \
    template <typename T, typename = void> \
    struct has_member_##U : std::false_type {}; \
    template <typename T> \
    struct has_member_##U<T, decltype(void(T::U))> : std::true_type {}


// usage:
DECLARE_MEMBER_CHECKER(counter);

template <class T>
int increment_counter(T& t){
    if constexpr(has_member_counter<T>{}){
        // eventually static_assert that counter is int, an integral type, or something else
        ++t.counter;
        return t.counter;
    } else {
        // counter is optional, always return 0
        return 0;
    }
}

struct s1{
    int counter = 0;
};
struct s2{
};

void foo(){
    s1 i;
    increment_counter(i);
}
void bar(){
    s2 i;
    increment_counter(i);
}

Similar examples can be made for member functions and free functions too.

Update (2024-02-14) require instead of SFINAE

Since C++20, require often is a simpler alternative to SFINAE. In particular, it helps to avoid the macros for generating structures.

The previous example for determining if there is a counter to increments would look like

template <class T>
int increment_counter(T& t){
    if constexpr(requires { void(T::counter); }){
        // eventually static_assert that counter is int, an integral type, or something else
        ++t.counter;
        //return t.counter;
    } else {
        // counter is optional, always return 0
        return 0;
    }
}

struct s1{
    int counter = 0;
};
struct s2{
};

void foo(){
    s1 i;
    increment_counter(i);
}
void bar(){
    s2 i;
    increment_counter(i);
}

In most cases, taking out the snippet of code from decltype (or std::void_t) and putting it into a requires expression with a ; is all that is needed.

For example, detecting a transparent comparator can be done with a templated function as

#include <map>

template <typename T>
constexpr auto is_transparent(){
    if constexpr(requires{ typename T::is_transparent; }){
        return std::true_type();
    } else {
        return std::false_type();
    }
}

struct comparator0{
    bool is_transparent;
};
struct comparator1{
    using is_transparent = int;
};
struct comparator2{
    using is_transparent = void;
};
struct incomplete;
struct comparator3{
    using is_transparent = incomplete;
};

static_assert(not is_transparent<comparator0>());
static_assert(    is_transparent<comparator1>());
static_assert(    is_transparent<comparator2>());
static_assert(    is_transparent<comparator3>());
static_assert(    is_transparent<std::less<>>());
static_assert(not is_transparent<std::map<int,int>::key_compare>());
static_assert(    is_transparent<std::map<int,int,std::less<>>::key_compare>());

Just like if constexpr helps to make the code easier to understand compared to function overloads, require removes the boilerplate necessary for querying the desired information.


Do you want to share your opinion? Or is there an error, some parts that are not clear enough?

You can contact me anytime.