How to write a type trait `is_container` or `is_vector`?

Is it possible to write a type trait whose value is true for all common STL structures (e.g., vector, set, map, ...)?

To get started, I'd like to write a type trait that is true for a vector and false otherwise. I tried this, but it doesn't compile:

template<class T, typename Enable = void>
struct is_vector {
  static bool const value = false;
};

template<class T, class U>
struct is_vector<T, typename boost::enable_if<boost::is_same<T, std::vector<U> > >::type> {
  static bool const value = true;
};

The error message is template parameters not used in partial specialization: U.


Solution 1:

Look, another SFINAE-based solution for detecting STL-like containers:

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

template<typename... Ts>
struct is_container_helper {};

template<typename T>
struct is_container<
        T,
        std::conditional_t<
            false,
            is_container_helper<
                typename T::value_type,
                typename T::size_type,
                typename T::allocator_type,
                typename T::iterator,
                typename T::const_iterator,
                decltype(std::declval<T>().size()),
                decltype(std::declval<T>().begin()),
                decltype(std::declval<T>().end()),
                decltype(std::declval<T>().cbegin()),
                decltype(std::declval<T>().cend())
                >,
            void
            >
        > : public std::true_type {};

Of course, you might change methods and types to be checked.

If you want to detect only STL containers (it means std::vector, std::list, etc) you should do something like this.

UPDATE. As @Deduplicator noted, container might not meet AllocatorAwareContainer requirements (e.g.: std::array<T, N>). That is why check on T::allocator_type is not neccessary. But you may check any/all Container requirements in a similar way.

Solution 2:

Actually, after some trial and error I found it's quite simple:

template<class T>
struct is_vector<std::vector<T> > {
  static bool const value = true;
};

I'd still like to know how to write a more general is_container. Do I have to list all types by hand?

Solution 3:

You would say that it should be simpler than that...

template <typename T, typename _ = void>
struct is_vector { 
    static const bool value = false;
};
template <typename T>
struct is_vector< T,
                  typename enable_if<
                      is_same<T,
                              std::vector< typename T::value_type,
                                           typename T::allocator_type >
                             >::value
                  >::type
                >
{
    static const bool value = true;
};

... But I am not really sure of whether that is simpler or not.

In C++11 you can use type aliases (I think, untested):

template <typename T>
using is_vector = is_same<T, std::vector< typename T::value_type,
                                          typename T::allocator_type > >;

The problem with your approach is that the type U is non-deducible in the context where it is used.

Solution 4:

While the other answers here that try to guess whether a class is a container or not might work for you, I would like to present you with the alternative of naming the type you want to return true for. You can use this to build arbitrary is_(something) traits types.

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

template<class T, class Alloc> 
struct is_container<std::vector<T, Alloc>> : public std::true_type {};

template<class K, class T, class Comp, class Alloc> 
struct is_container<std::map<K, T, Comp, Alloc>> : public std::true_type {};

And so on.

You will need to include <type_traits> and whatever classes you add to your rules.

Solution 5:

Why not do something like this for is_container?

template <typename Container>
struct is_container : std::false_type { };

template <typename... Ts> struct is_container<std::list<Ts...> > : std::true_type { };
template <typename... Ts> struct is_container<std::vector<Ts...> > : std::true_type { };
// ...

That way users can add their own containers by partially-specializing. As for is_vector et-al, just use partial specialization as I did above, but limit it to only one container type, not many.