More C++ Idioms/Member Detector

= Member Detector =

Intent
To detect the presence of a specific member attribute, function or type in a class.

Motivation
Compile-time reflection capabilities are the cornerstone of C++ template meta-programming. Type traits libraries such as Boost.TypeTraits and TR1  header provide powerful ways of extracting information about types and their relationships. Detecting the presence of a data member in a class is also an example of compile-time reflection.

Solution and Sample Code
Member detector idiom is implemented using the Substitution Failure Is Not An Error (SFINAE) idiom. The following class template DetectX is a meta-function that determines whether type T has a data or function member named X in it or not. Note that the type of the data member X does not matter nor does the return value and arguments of the member function (if it is one).

This idiom works by creating a controlled ambiguity during compilation and recovering from that using the SFINAE idiom. First proxy class, Fallback, has a data member of the same name that we want to detect the presence of. Class Derived inherits from both T and Fallback. As a result, Derived class will have at least one data member named X. Potentially, Derived class may have two X data members if T also has one.

The Check template is used to create controlled ambiguity. Check template takes two parameters. First is a type parameter and the second is an instance of that type. For example, Check would be a valid instantiation. Two overloaded functions named func create an overload-set as often done in the SFINAE idiom. The first func function can be instantiated only if the address of data member U::X can be taken unambiguously. Address of U::X can be taken if there is exactly one X data member in the Derived class; i.e., T does not have data member X. If T has X in it, the address of U::X can't be taken without further disambiguation and therefore the instantiation of the first func fails and the other function is chosen, all without an error. Note the difference between the return types of the two func functions. The first function returns a reference to array of size one whereas the second one returns a reference to an array of size two. This difference in the sizes allows us to identify which function was instantiated.

Finally, a boolean value is exposed, which is true only if the sizeof the return type of the function is two. That is, when the second func is instantiated only because T has X data member.

For every different member that is to be detected, the above class template needs to change. A macro would be preferable in such cases. The following sample code demonstrates the use of a macro.

Using C++11 features, this example can be rewritten so that the controlled ambiguity is created using the  specifier instead of a Check template and a pointer to member. The result can then be wrapped inside a class inheriting from  to provide an interface identical to the type predicates present in the standard header.

Detecting member types

The above example can be adapted to detect existence of a member type, be it a nested class or a typedef, even if it is incomplete. This time, the ambiguity would be created by adding to Fallback a nested type with the same name as the macro argument.

This is much like the member detection macro that declares a data member of the same name as the macro argument. An overload set of two test functions is then created, just like the other examples. The first version can only be instantiated if the type of U::Type can be used unambiguously. This type can be used only if there is exactly one instance of Type in Derived, i.e. there is no Type in T. If T has a member type Type, it is garanteed to be different than Fallback::Type, since the latter is a unique type, hence creating the ambiguity. This leads to the second version of test being instantiated in case the substitution fails, meaning that T has indeed a member type Type. Since no objects are ever created (this is entirely resolved by compile-time type checking), Type can be an incomplete type or be ambiguous inside T; only the name matters. We then wrap the result in a class inheriting from, just like before to provide the same interface as the standard library.

Detecting overloaded member functions

A variation of the member detector idiom can be used to detect existence of a specific member function in a class even if it is overloaded. The HasPolicy template above checks if T has a member function called policy that takes two parameters ARG1, ARG2 and returns RESULT. Instantiation of Check template succeeds only if U has a U::policy member function that takes two parameters and returns RESULT. Note that the first type parameter of Check template is a type whereas the second parameter is a pointer to a member function in the same type. If Check template cannot be instantiated, the only remaining func that returns an int is instantiated. The size of the return value of func eventually determines the answer of the type-trait: true or false.

Known Issues
Will not work if the class checked for member is declared final (C++11 keyword).

Will not work to check for a member of a union (unions cannot be base classes).

C++17
C++17 added  to make it easier to perform metaprogramming tasks. simply expands to, but it is still useful because of SFINAE. The benefits of using this approach are that it addresses the known issues of not supporting final classes and unions.

C++20
C++20 added the  statement, which is much more powerful than the simple member checkers above as it can check the validity of more complex expressions, but it too can be a member checker. It also makes code simpler and more readable because of its simplicity.

Related Idioms

 * Substitution Failure Is Not An Error (SFINAE)