Determine if a type is an STL container at compile time
First, you define your primary template, which will have a member which is false in the default case:
template <typename T>
struct is_cont {
static const bool value = false;
};
Then you will define partial specializations for your container types which have a value of true instead:
template <typename T,typename Alloc>
struct is_cont<std::vector<T,Alloc> > {
static const bool value = true;
};
Then for a type X that you want to check, use it like
if (is_cont<X>::value) { ... }
Pursuing the suggestion that a generic compiletime test for
has-an-stl-container-like-interface would be an appropriate
solution, this one defines an stl-like container T
by the interface:
T::iterator T::begin();
T::iterator T::end();
T::const_iterator T::begin() const;
T::const_iterator T::end() const;
*T::iterator is T::value_type &
*T::const_iterator is T::value_type const &
Additional requirements, e.g. a size()
method, could be added in an obvious
fashion, or other canonical type interfaces probed at compiletime in an obvious
similar way.
#ifndef IS_STL_CONTAINER_LIKE_H
#define IS_STL_CONTAINER_LIKE_H
#include <type_traits>
template<typename T>
struct is_stl_container_like
{
typedef typename std::remove_const<T>::type test_type;
template<typename A>
static constexpr bool test(
A * pt,
A const * cpt = nullptr,
decltype(pt->begin()) * = nullptr,
decltype(pt->end()) * = nullptr,
decltype(cpt->begin()) * = nullptr,
decltype(cpt->end()) * = nullptr,
typename A::iterator * pi = nullptr,
typename A::const_iterator * pci = nullptr,
typename A::value_type * pv = nullptr) {
typedef typename A::iterator iterator;
typedef typename A::const_iterator const_iterator;
typedef typename A::value_type value_type;
return std::is_same<decltype(pt->begin()),iterator>::value &&
std::is_same<decltype(pt->end()),iterator>::value &&
std::is_same<decltype(cpt->begin()),const_iterator>::value &&
std::is_same<decltype(cpt->end()),const_iterator>::value &&
std::is_same<decltype(**pi),value_type &>::value &&
std::is_same<decltype(**pci),value_type const &>::value;
}
template<typename A>
static constexpr bool test(...) {
return false;
}
static const bool value = test<test_type>(nullptr);
};
#endif
Here is a test program, built with GCC 4.7.2, clang 3.2, Intel C++ 13.1.1:
#include "is_stl_container_like.h"
// Testing ...
#include <iostream>
#include <vector>
#include <array>
#include <functional>
using namespace std;
template<class C>
struct polymorphic : private C
{
typedef typename C::value_type value_type;
typedef typename C::iterator iterator;
typedef typename C::const_iterator const_iterator;
virtual ~polymorphic(){}
virtual const_iterator begin() const {
return C::begin();
}
virtual iterator begin() {
return C::begin();
}
virtual const_iterator end() const {
return C::end();
}
virtual iterator end() {
return C::end();
}
};
template<class C>
struct reject : private C
{
typedef typename C::value_type value_type;
typedef typename C::iterator iterator;
typedef typename C::const_iterator const_iterator;
const_iterator begin() {
return C::begin();
}
iterator begin() const {
return C::begin();
}
const_iterator end() {
return C::end();
}
iterator end() const {
return C::end();
}
};
int main()
{
cout << is_stl_container_like<vector<int> const >::value << endl; // Yes
cout << is_stl_container_like<array<int,42>>::value << endl; // Yes
cout << is_stl_container_like<polymorphic<vector<int>>>::value << endl; // Yes
cout << is_stl_container_like<function<int(int)>>::value << endl; // No
cout << is_stl_container_like<int>::value << endl; // No
cout << is_stl_container_like<reject<vector<int>>>::value << endl; //No
}
Many of the already proposed solutions are verbose for detecting STL containers.
They focus on the characteristics that all containers possess, instead of explicitly stating what the containers are.
If you wanted to create your own containers and have them evaluated with a true type, I'd recommend the other solutions. If you only want to validate legitimate STL containers, and not STL-like containers, consider using the following implementation, as it provides precise STL container detection:
#include <deque>
#include <forward_list>
#include <list>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <tuple>
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
#include <type_traits>
//specialize a type for all of the STL containers.
namespace is_stl_container_impl{
template <typename T> struct is_stl_container:std::false_type{};
template <typename T, std::size_t N> struct is_stl_container<std::array <T,N>> :std::true_type{};
template <typename... Args> struct is_stl_container<std::vector <Args...>>:std::true_type{};
template <typename... Args> struct is_stl_container<std::deque <Args...>>:std::true_type{};
template <typename... Args> struct is_stl_container<std::list <Args...>>:std::true_type{};
template <typename... Args> struct is_stl_container<std::forward_list <Args...>>:std::true_type{};
template <typename... Args> struct is_stl_container<std::set <Args...>>:std::true_type{};
template <typename... Args> struct is_stl_container<std::multiset <Args...>>:std::true_type{};
template <typename... Args> struct is_stl_container<std::map <Args...>>:std::true_type{};
template <typename... Args> struct is_stl_container<std::multimap <Args...>>:std::true_type{};
template <typename... Args> struct is_stl_container<std::unordered_set <Args...>>:std::true_type{};
template <typename... Args> struct is_stl_container<std::unordered_multiset<Args...>>:std::true_type{};
template <typename... Args> struct is_stl_container<std::unordered_map <Args...>>:std::true_type{};
template <typename... Args> struct is_stl_container<std::unordered_multimap<Args...>>:std::true_type{};
template <typename... Args> struct is_stl_container<std::stack <Args...>>:std::true_type{};
template <typename... Args> struct is_stl_container<std::queue <Args...>>:std::true_type{};
template <typename... Args> struct is_stl_container<std::priority_queue <Args...>>:std::true_type{};
}
//type trait to utilize the implementation type traits as well as decay the type
template <typename T> struct is_stl_container {
static constexpr bool const value = is_stl_container_impl::is_stl_container<std::decay_t<T>>::value;
};
Note the use of std::decay
to avoid incorrect type deduction based on type qualifiers. Also, we've utilized inheriting std::true_type
and std::false_type
to avoid specifying the ::type
types ourselves. C++11 variadic templates are used to deduce the n amount of template type parameters needed to construct the containers.
Using the implementation is as you would expect:
std::cout << std::boolalpha;
std::cout << is_stl_container<std::vector<int>>::value << '\n';
std::cout << is_stl_container<std::vector<int>const&>::value << '\n';
std::cout << is_stl_container<int>::value << '\n';
prints:
true
true
false
Note: the following code is taken from an excellent utility called pretty-print written by @Kerrek SB (a topic on it at stackoverflow).
Disclaimer : I don't know if I'm allowed to copy and paste this code here without taking permission from the original author. @Kerrek, let me know if you've any issue. :-)
You can use this classs template:
template<typename T>
struct is_container : std::integral_constant<bool, has_const_iterator<T>::value && has_begin_end<T>::beg_value && has_begin_end<T>::end_value>
{ };
Usage:
std::cout << is_container<std::vector<int>>::value << std::endl; //true
std::cout << is_container<std::list<int>>::value << std::endl; //true
std::cout << is_container<std::map<int>>::value << std::endl; //true
std::cout << is_container<std::set<int>>::value << std::endl; //true
std::cout << is_container<int>::value << std::endl; //false
Note that is_container
needs following helper class templates:
template<typename T>
struct has_const_iterator
{
private:
typedef char yes;
typedef struct { char array[2]; } no;
template<typename C> static yes test(typename C::const_iterator*);
template<typename C> static no test(...);
public:
static const bool value = sizeof(test<T>(0)) == sizeof(yes);
typedef T type;
};
template <typename T>
struct has_begin_end
{
template<typename C> static char (&f(typename std::enable_if<
std::is_same<decltype(static_cast<typename C::const_iterator (C::*)() const>(&C::begin)),
typename C::const_iterator(C::*)() const>::value, void>::type*))[1];
template<typename C> static char (&f(...))[2];
template<typename C> static char (&g(typename std::enable_if<
std::is_same<decltype(static_cast<typename C::const_iterator (C::*)() const>(&C::end)),
typename C::const_iterator(C::*)() const>::value, void>::type*))[1];
template<typename C> static char (&g(...))[2];
static bool const beg_value = sizeof(f<T>(0)) == 1;
static bool const end_value = sizeof(g<T>(0)) == 1;
};