Reflecting mirrors

Detect transparent comparators

Notes published the
3 minutes to read, 666 words
Categories: c++
Keywords: c++ meta-programming reflection

After writing about std::map::lower_bound and transparent comparators, a question that followed up was "How to detect at compile time if the comparator is transparent or not?".

A comparator is transparent if it defines an is_transparent type.

struct comparator0{
	bool is_transparent;
	// operator() and eventually others
};
struct comparator1{
	using is_transparent = int;
	// operator() and eventually others
};
struct comparator2{
	using is_transparent = void;
	// operator() and eventually others
};

Both comparator1 and comparator2 are transparent comparators, as long as the tag type is_transparent is defined, thus something like using a = comparator1::is_transparent; is valid. comparator0 is thus not a transparent comparator.

Looking back at how to detect member variables at compile time, it is possible to define a similar set of structures for querying if is_transparent is defined or not

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

template <typename T>
struct is_transparent_comparator<T, typename T::is_transparent> : std::true_type {};

struct comparator0{
	bool is_transparent;
	// operator() and eventually others
};
struct comparator1{
	using is_transparent = int;
	// operator() and eventually others
};
struct comparator2{
	using is_transparent = void;
	// operator() and eventually others
};


static_assert(is_transparent_comparator<compare1>::value);
static_assert(is_transparent_comparator<my_map::key_compare>::value);
static_assert(not is_transparent_comparator<compare2>::value);
static_assert(not is_transparent_comparator<std::map<int,int>::key_compare>::value);

It seems to work correctly, but it fails to detect that std::map<int, int, std::less<>> has a transparent comparator.

The issue can be reproduced by using an incomplete type.

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

template <typename T>
struct is_transparent_comparator<T, typename T::is_transparent> : std::true_type {};

struct incomplete;

struct comparator3{
	using is_transparent = incomplete;
	// operator() and eventually others
};

static_assert(is_transparent_comparator<comparator3>::value); // fails to compile

An additional indirection helps to support incomplete types

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

template <typename T>
struct is_transparent_comparator<T, typename std::void_t<typename T::is_transparent>> : std::true_type {};

struct incomplete;
struct comparator3{
	using is_transparent = incomplete;
	// operator() and eventually others
};

static_assert(is_transparent_comparator<comparator3>::value); // compiles

This is not necessary for detecting member variables, as a member variable cannot have an incomplete type.

Note that those tag types are used only for compile-time meta-programming. In general, they are not used for anything else, which is why some standard libraries use an incomplete type instead of void or built-in types.

TL;DR:

#include <type_traits>
#include <map>

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

template <typename T>
struct is_transparent_comparator<T, typename std::void_t<typename T::is_transparent>> : std::true_type {};

struct comparator0{
	bool is_transparent;
	// operator() and eventually others
};
struct comparator1{
	using is_transparent = int;
	// operator() and eventually others
};
struct comparator2{
	using is_transparent = void;
	// operator() and eventually others
};
struct incomplete;
struct comparator3{
	using is_transparent = incomplete;
	// operator() and eventually others
};


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

Note that if you do not have access to C++17 yet, then template <class> struct my_void_t { using type = void; }; is a good enough replacement for std::void_t.


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

You can contact me anytime.