Simplest implementation of “lightweight type categorization idiom”?

  • A+
Category:Languages

My goal is to implement a predicate that detects the presence of a nested using alias (or typedef) that acts as a light-weight tag to indicate that a class has some attribute (for the purposes of generic programming). For example, a has_my_tag<T> predicate should behave as follows:

struct A {   using my_tag = void; };  struct B {};  int main() {     static_assert(has_my_tag<A>::value, "");  // evaluate to true if my_tag=void is present     static_assert(!has_my_tag<B>::value, ""); // false otherwise } 

User @JoelFalcou called this the "lightweight type categorization idiom" and provided a solution in this answer. I have been unable to find any references for an idiom of that name (do you know of any?) Here's Joel's implementation of has_my_tag<>:

template<class T, class R = void>   struct enable_if_type { typedef R type; };  template<class T, class Enable = void> struct has_my_tag : std::false_type {};  template<class T> struct has_my_tag<T, typename enable_if_type<typename T::my_tag>::type> :  std::true_type {}; 

And here is a working version on the Compiler Explorer: https://godbolt.org/z/EEOBb-

I have come up with the following simplified version:

template<class T, class Enable = void> struct has_my_tag : std::false_type {};  template<class T> struct has_my_tag<T, typename T::my_tag> : std::true_type {}; 

https://godbolt.org/z/yhkHp7

My Questions: Is the simplified version an acceptable way to implement the idiom? Are there circumstances where it would fail? Is there a simpler version that works in C++11? Which version should I prefer?

From what I understand, Joel's version would allow my_tag to alias any type, whereas my version requires my_tag to alias void. But given the goal of tagging types for light-weight predicate testing, I am not clear which version is to be preferred.

Auxiliary questions: Also, are there other names for this idiom? Is it used in any libraries that I could investigate? So far I have not found a name that brings up any search results.

 


For your setup, there is no difference between the original version and yours. Both use SFINAE to select the correct has_my_tag. Your version does however constrain your typedef/using to be my_tag=void. if my_tag is typedef'd as any other type, your specialization will not match, and you will end up instantiating the primary template as can be seen here.

The reason for this is that where you instanciate the templates in main, static_assert(has_my_tag<A>::value, ""); you are not specifying the second parameter, so the default (void) is used, that is has_my_tag<A,void>::value

Your specialization must match this to be considered.

The usage of enable_if_type, (basically doing the job of void_t in c++17) is to enable SFINAE on the ::type member of T, but then always result in void, such that your specialization will match when ::type exists, regardless of the type of the my_tag typedef.

This allows you to just worry about whether it exists, and not its type;

Personally I would use the approach that doesn't depend on my_type being typedef'd as void, either the enable_if_type version, or something like...

#include <iostream> #include <type_traits>  struct A {   using my_tag = void; };  struct B {};  struct C {   using my_tag = int; // with void_t also works with my_tag = int };  struct D {   struct my_tag{}; //or some struct as the tag };  // same as your enable_if_type template <typename...> using void_t = void;   template<class T, class Enable = void> struct has_my_tag : std::false_type {};  template<class T> struct has_my_tag<T, void_t<typename T::my_tag>> : std::true_type {};   int main() {     std::cout << has_my_tag<A>::value << std::endl;     std::cout << has_my_tag<B>::value << std::endl;     std::cout << has_my_tag<C>::value << std::endl;     std::cout << has_my_tag<D>::value << std::endl;     return 0; } 

Demo

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: