Determine if a type is an STL container at compile time

21,399

Solution 1

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) { ... } 

Solution 2

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;
};

Solution 3

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
}

Solution 4

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

Solution 5

In C++20, you might use concept,

up to you to add more checks from what you consider as container, but it might look like:

template <typename T>
concept Container = requires(T t)
{
    std::begin(t);
    std::end(t);
};

Usage example.

There are already existing concepts instandard which might interest you as:

std::ranges::range

Share:
21,399

Related videos on Youtube

Xander Tulip
Author by

Xander Tulip

Updated on November 14, 2021

Comments

  • Xander Tulip
    Xander Tulip over 2 years

    I would like to write a template that will determine if a type is an stl container at compile time.  

    I've got the following bit of code:

    struct is_cont{};
    struct not_cont{};
    
    template <typename T>
    struct is_cont { typedef not_cont result_t; };
    

    but I'm not sure how to create the necessary specializations for std::vector<T,Alloc>, deque<T,Alloc>, set<T,Alloc,Comp> etc...

    • Nicol Bolas
      Nicol Bolas about 12 years
      I would be curious to know why it needs to exactly be one of the standard library STL containers. Wouldn't it make sense to check the properties of the container, so that if something else conformed to those properties, it could be used without problems?
    • MSalters
      MSalters about 12 years
      std::unordered_map wasn't in the STL, but it is now in C++11. Do you consider it an STL type?
    • Billy ONeal
      Billy ONeal over 11 years
      @MSalters: It does meet the container requirements.
    • razeh
      razeh about 11 years
      Once you've decided that a container is an STL container, what will you do?
    • Chenna V
      Chenna V almost 7 years
      Am curious, in what scenario/use-case one would be using such a template? Because any generic code that would use a vector won't be able to use an associative container such as map. If the user of your template is just asking this so they can iterate then, the non-member STL functions exist std::begin() and std::end() (cbegin/cend) are primarily for this purpose
    • aniliitb10
      aniliitb10 over 5 years
      If you are not strictly looking for STL container but interable in general, then here is an answer: stackoverflow.com/a/53967057/3701834
  • Xander Tulip
    Xander Tulip about 12 years
    I had the same approach, the problem is that STL containers have more than 1 template param eg: Alloc, Comp etc...
  • bitmask
    bitmask about 12 years
    Note that this will fail, if you provide non-default parameters to the vector. You should forward all template parameters, in order to be generic.
  • Vaughn Cato
    Vaughn Cato about 12 years
    True -- I've made my answer more generic.
  • bitmask
    bitmask about 12 years
    Nice thing, but it will recognise non-std types that happen to follow std conventions (i.e. having iterators and begin/end pairs). This is not at all uncommon!
  • Nawaz
    Nawaz about 12 years
    @bitmask: Yes, because it is completely generic.:-)
  • Xander Tulip
    Xander Tulip about 12 years
    @Nawaz: That is a nice approach, but its a little more than what was required.
  • bitmask
    bitmask about 12 years
    No argument there. But genericity makes sense for the pretty-printer, yet is only suited as a mere heuristic for this question.
  • andybuckley
    andybuckley over 11 years
    This looks like it's specifically an internal of the Boost Spirit parser library. It's not clear to me from the documentation whether it can be used as a more general determiner of container type, nor what exactly a positive response from this trait guarantees about that type! Maybe someone knows better...
  • Viktor Sehr
    Viktor Sehr about 10 years
    Doesnt work in Visual Studio 2012, always returns false
  • Nawaz
    Nawaz about 10 years
    @ViktorSehr: I don't trust VS2012.
  • kirill_igum
    kirill_igum over 9 years
    boost is made to work with stdlib. afterall, most of the new std lib stuff comes from boost. I'm not sure how to address your comment besides by looking into the code but keep in mind that comparing to other solutions, boost has been reviewed and tested.
  • andybuckley
    andybuckley over 9 years
    I wasn't criticising the correctness of Boost nor its relation to the STL, but noting that this part of it is not obviously intended for general use. The Spirit parser library and the template metaprogramming libraries on which it depends are not on track for STL inclusion, AFAIK -- some bits of Boost are more "standard" than others. The documentation notes that this is primarily intended for use by Spirit's >> and << operator overloads. Did you check that it works for STL container types?
  • kreuzerkrieg
    kreuzerkrieg over 8 years
    Nice. but does not compile with VS2015 :(
  • Trevor Hickey
    Trevor Hickey over 8 years
    You may need to ensure that you are building with a newer language standard. It works in C++14, as seen here: ideone.com/PuD98p
  • kreuzerkrieg
    kreuzerkrieg over 8 years
    yeah, tested it with coliru, works fine. maybe it is not implemented yet. what part of c++14 standard this stuff corresponds?
  • Trevor Hickey
    Trevor Hickey over 8 years
    I suspect it's the true_type, and false_type as they are intended for C++17. It depends on what your compiler error is though. You can always implement the same thing without true_type and false_type, but its more verbose.
  • kreuzerkrieg
    kreuzerkrieg over 8 years
    e:\documents\visual studio 2015\projects\consoleapplication6\consoleapplication6\consol‌​eapplication6.cpp(23‌​): error C2976: 'std::array': too few template arguments c:\program files (x86)\microsoft visual studio 14.0\vc\include\tuple(687): note: see declaration of 'std::array' e:\documents\visual studio 2015\projects\consoleapplication6\consoleapplication6\consol‌​eapplication6.cpp(23‌​): error C3203: 'array': unspecialized class template can't be used as a template argument for template parameter 'T', expected a real type
  • kreuzerkrieg
    kreuzerkrieg over 8 years
    e:\documents\visual studio 2015\projects\consoleapplication6\consoleapplication6\consol‌​eapplication6.cpp(23‌​): error C3211: 'is_stl_container_impl::is_stl_container<int>': explicit specialization is using partial specialization syntax, use template <> instead e:\documents\visual studio 2015\projects\consoleapplication6\consoleapplication6\consol‌​eapplication6.cpp(23‌​): note: see declaration of 'is_stl_container_impl::is_stl_container<int>'
  • kreuzerkrieg
    kreuzerkrieg over 8 years
    looks like it cannot specialize with argument pack or something
  • Piotr Skotnicki
    Piotr Skotnicki about 8 years
    is_stl_container<std::array<Args...>> seems wrong
  • Shital Shah
    Shital Shah almost 8 years
    This code had lot of updated. I've re-extracted the code from prettyprint library in to a simple drop-in header. You can find this header as well as example code in my blog post: shitalshah.com/p/writing-generic-container-function-in-c11
  • Gabriel de Grimouard
    Gabriel de Grimouard almost 7 years
    your code does not work well with the universal references. When I try to use it in a template function and send my container in parameter without using the universal reference it work well, but with the universal reference the vector is not considered as a container and then is_container<T>::value is false.
  • Nawaz
    Nawaz almost 7 years
    @GabrieldeGrimouard: The example is not designed to work with "universal reference" (whose standard name is "forwarding reference" BTW), but you can modify it as per your need.
  • Baj Mile
    Baj Mile over 4 years
    Can not compile in VS2017
  • Michiel uit het Broek
    Michiel uit het Broek almost 4 years
    Maybe a bit late, but what is argument *pv doing? Because I don't see it being used in the return statement and the compiler (gcc 7.5.0) complains about it being unused.