Alex
Alex

Reputation: 1175

Recursive type check using templates

I've got a couple of type checking templates: is_object from type_traits and isSupportedContainer implemented the following way:

template <class T>
struct isSupportedContainer
    : public false_type {};

template <class T>
struct isSupportedContainer < list<T> >
    : public true_type {};

/*other containers*/

I want to make a recursive check isSupported applying itself not only to the container type, but also to the contained type. The current implementation:

// using std::__and_ and std::for from type_traits
template <class T>
struct isSupported
    : public __and_ < __or_ < is_object<T>, isSupportedContainer<T> >,
                      isSupported <T> > {};

When I call isSupported < vector<int> >::value, it generates a bunch of compilation errors (shortened):

In instantiation of 'struct std::__and_<...>, isSupportedContainer<std::vector<...> > >, isSupported<std::vector<...> > >':
required from 'struct isSupported<std::vector<int> >'
required from /*its call in main()*/
error: invalid use of incomplete type 'std::conditional<true, isSupported<std::vector<int> >, std::__or_<...>, isSupportedContainer<std::vector<...> > > >::type'
 struct __and_<_B1, _B2>
        ^
In /*file containing isSupported and isSupportedContainer*/:
error: declaration of 'std::conditional<true, isSupported<std::vector<int> >, std::__or_<...> >::type'
 struct isSupported
        ^
In function 'int main()':
error: 'value' is not a member of 'isSupported<std::vector<int> >'
 cout << isSupported < vector<int> >::value;
         ^

So, how can such a check be implemented?

Examples: assuming list and vector as supported classes vector<list<int>> is also supported and vector<list<vector<string>>> is not

UPD: a working version
UPD2: no, not working

template <class T>
struct isSupported
    : public isSupportedSimpleObject<T> {};    //is_object turned out to be a wrong thing

template <template<class> class T, class U>
struct isSupported < T<U> >
    : public __and_ < isSupportedContainer<T<U>>,
                      isSupported <U> > {};

Upvotes: 1

Views: 548

Answers (1)

You could probably simplify the whole calculation if you use enable_if in the initial substitution.

#include <type_traits>
#include <list>
#include <vector>
#include <string>

template <class T>
struct isSupportedContainer : std::false_type {};

template <class T>
struct isSupportedContainer < std::list<T> > : std::true_type {};

template <class T>
struct isSupportedContainer < std::vector<T> > : std::true_type {};

template <class T, typename = void> // #1
struct isSupported : std::integral_constant<bool, std::is_object<T>::value> {};

template <class Cont> // #2
struct isSupported<Cont, typename std::enable_if<isSupportedContainer<Cont>::value>::type>
  : isSupported<typename Cont::value_type> {};


int main() {
    static_assert(isSupported<std::vector<int>>::value,"");
    static_assert(isSupported<std::vector<std::list<int>>>::value,"");
    static_assert(isSupported<std::string>::value,"");
    static_assert(!isSupported<int&>::value,"");
    return 0;
}

Live Demo

This technique (if it is indeed what you are after) is based on the principle for which std::void_t was added to C++17

The key points are as follows:

  1. The primary template definition accepts two type parameters. the first is named T, and the other is unnamed and has a default type of void. This version simply checks your base case.
  2. When you instantiate the template, the compiler matches the primary instance isSupported<YourType, void>.
    Now the compiler checks if there is any specialization that matches the argument types <YourType, void>, so it looks at the specialization that was provided. std::enable_if is used to determine it the type is in fact a supported container, and if such it returns void, and we match the types deduced originally <YourType, void>. Therefore the compiler takes the specialization as the better match, and we do a recursive check.
  3. If however the type supplied is not a supported container, std::enable_if has no type member, and SFINAE kicks in. The substitution fails, and we return to the base case we found in (1), which does the basic check.

Upvotes: 2

Related Questions