25

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...

keineahnung2345
  • 2,635
  • 4
  • 13
  • 28
Xander Tulip
  • 1,438
  • 2
  • 17
  • 32
  • 6
    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? – Nicol Bolas Feb 23 '12 at 05:01
  • 2
    `std::unordered_map` wasn't in the STL, but it is now in C++11. Do you consider it an STL type? – MSalters Feb 23 '12 at 12:00
  • @MSalters: It *does* meet the container requirements. – Billy ONeal Dec 09 '12 at 23:00
  • Once you've decided that a container is an STL container, what will you do? – razeh May 01 '13 at 13:45
  • 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 – Chenna V Jun 25 '17 at 18:30
  • If you are not strictly looking for `STL container` but `interable` in general, then here is an answer: https://stackoverflow.com/a/53967057/3701834 – aniliitb10 Dec 29 '18 at 05:53

9 Answers9

31

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;
};
Seth Johnson
  • 14,762
  • 6
  • 59
  • 85
Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • 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! – bitmask Feb 23 '12 at 05:16
  • 2
    @bitmask: Yes, because it is completely generic.:-) – Nawaz Feb 23 '12 at 05:17
  • 1
    @Nawaz: That is a nice approach, but its a little more than what was required. – Xander Tulip Feb 23 '12 at 05:20
  • No argument there. But genericity makes sense for the pretty-printer, yet is only suited as a mere heuristic for this question. – bitmask Feb 23 '12 at 05:22
  • Doesnt work in Visual Studio 2012, always returns false – Viktor Sehr Mar 10 '14 at 14:35
  • 5
    @ViktorSehr: I don't trust VS2012. – Nawaz Mar 11 '14 at 06:49
  • 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: http://shitalshah.com/p/writing-generic-container-function-in-c11/ – Shital Shah Jun 05 '16 at 09:38
  • 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::value is false. – Gabrielle de Grimouard Jun 20 '17 at 16:21
  • @GabrieldeGrimouard: The example is not designed to work with "universal reference" (whose standard name is ["forwarding reference"](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4164.pdf) BTW), but you can modify it as per your need. – Nawaz Jun 21 '17 at 09:51
  • Can not compile in VS2017 – Baj Mile Nov 24 '19 at 10:36
23

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) { ... } 
Vaughn Cato
  • 63,448
  • 5
  • 82
  • 132
19

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
Trevor Hickey
  • 36,288
  • 32
  • 162
  • 271
  • Nice. but does not compile with VS2015 :( – kreuzerkrieg Oct 26 '15 at 06:55
  • You may need to ensure that you are building with a newer language standard. It works in C++14, as seen here: http://ideone.com/PuD98p – Trevor Hickey Oct 26 '15 at 15:00
  • yeah, tested it with coliru, works fine. maybe it is not implemented yet. what part of c++14 standard this stuff corresponds? – kreuzerkrieg Oct 26 '15 at 16:19
  • 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. – Trevor Hickey Oct 26 '15 at 17:02
  • e:\documents\visual studio 2015\projects\consoleapplication6\consoleapplication6\consoleapplication6.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\consoleapplication6.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 Oct 27 '15 at 04:48
  • e:\documents\visual studio 2015\projects\consoleapplication6\consoleapplication6\consoleapplication6.cpp(23): error C3211: 'is_stl_container_impl::is_stl_container': explicit specialization is using partial specialization syntax, use template <> instead e:\documents\visual studio 2015\projects\consoleapplication6\consoleapplication6\consoleapplication6.cpp(23): note: see declaration of 'is_stl_container_impl::is_stl_container' – kreuzerkrieg Oct 27 '15 at 04:48
  • looks like it cannot specialize with argument pack or something – kreuzerkrieg Oct 27 '15 at 04:49
  • 1
    `is_stl_container>` seems wrong – Piotr Skotnicki Feb 24 '16 at 15:15
18

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
}
Mike Kinghan
  • 55,740
  • 12
  • 153
  • 182
  • 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. – Michiel uit het Broek Jun 08 '20 at 12:50
9

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

Jarod42
  • 203,559
  • 14
  • 181
  • 302
2

There is is_container in boost http://www.boost.org/doc/libs/1_51_0/libs/spirit/doc/html/spirit/advanced/customize/is_container.html

is_container<C>::type --- Result of the metafunction that evaluates to mpl::true_ if a given type, C, is to be treated as a container, mpl::false_ otherwise Generally, any implementation of is_container needs to behave as if if was a MPL Boolean Constant..

kirill_igum
  • 3,953
  • 5
  • 47
  • 73
  • 2
    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... – andybuckley Jan 07 '13 at 20:54
  • 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. – kirill_igum Dec 16 '14 at 02:16
  • 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? – andybuckley Dec 16 '14 at 09:59
2

Tested with MSVC 2019:

template<class C>
struct IsContainer {
private:
    template<class D>
    static constexpr auto hasValueType() -> decltype(typename D::value_type(), std::true_type()) {
        return {};
    }

    template<class D>
    static constexpr auto hasIteratorAlias() -> decltype(typename D::iterator(), std::true_type()) {
        return {};
    }

    template<class D>
    static constexpr std::false_type hasIteratorAlias(...) {
        return {};
    }

    template<class D>
    static constexpr auto hasConstIteratorAlias() -> decltype(typename D::const_iterator(), std::true_type()) {
        return {};
    }

    template<class D>
    static constexpr auto hasBegin() -> decltype(decltype(std::begin(std::declval<D>())){}, std::true_type()) {
        return {};
    }

    template<class D>
    static constexpr std::false_type hasBegin(...) {
        return {};
    }

    template<class D>
    static constexpr auto hasEnd() -> decltype(decltype(std::end(std::declval<D>())){}, std::true_type()) {
        return {};
    }

    template<class D>
    static constexpr std::false_type hasEnd(...) {
        return {};
    }

    template<class D>
    static constexpr std::false_type hasConstIteratorAlias(...) {
        return {};
    }

    template<class D>
    static constexpr std::false_type hasValueType(...) {
        return {};
    }

public:
    constexpr static bool value = hasValueType<C>().value && 
        hasIteratorAlias<C>().value && 
        hasConstIteratorAlias<C>().value && 
        hasBegin<C>().value && 
        hasEnd<C>().value;
    
    constexpr bool operator()() const {
        return value;
    }
};

Usage:

std::vector<int> vec;
int x = 0;
float y = 0.f;
std::array<int, 1> arr{};
    
constexpr auto val = IsContainer<decltype(vec)>()();
constexpr auto val2 = IsContainer<decltype(x)>()();
constexpr auto val3 = IsContainer<decltype(y)>()();
constexpr auto val4 = IsContainer<decltype(arr)>()();
    
std::cout << static_cast<bool>(val) << '\n';
std::cout << static_cast<bool>(val2) << '\n';
std::cout << static_cast<bool>(val3) << '\n';
std::cout << static_cast<bool>(val4) << '\n';

Output:

1
0
0
1
Marc Dirven
  • 309
  • 2
  • 18
1

This code defines traits for container. It's originally from prettyprint library:

//put this in type_utils.hpp 
#ifndef commn_utils_type_utils_hpp
#define commn_utils_type_utils_hpp

#include <type_traits>
#include <valarray>

namespace common_utils { namespace type_utils {
    //from: https://raw.githubusercontent.com/louisdx/cxx-prettyprint/master/prettyprint.hpp
    //also see https://gist.github.com/louisdx/1076849
    namespace detail
    {
        // SFINAE type trait to detect whether T::const_iterator exists.

        struct sfinae_base
        {
            using yes = char;
            using no  = yes[2];
        };

        template <typename T>
        struct has_const_iterator : private sfinae_base
        {
        private:
            template <typename C> static yes & test(typename C::const_iterator*);
            template <typename C> static no  & test(...);
        public:
            static const bool value = sizeof(test<T>(nullptr)) == sizeof(yes);
            using type =  T;

            void dummy(); //for GCC to supress -Wctor-dtor-privacy
        };

        template <typename T>
        struct has_begin_end : private sfinae_base
        {
        private:
            template <typename C>
            static yes & 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>::type *);

            template <typename C> static no & f(...);

            template <typename C>
            static yes & 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*);

            template <typename C> static no & g(...);

        public:
            static bool const beg_value = sizeof(f<T>(nullptr)) == sizeof(yes);
            static bool const end_value = sizeof(g<T>(nullptr)) == sizeof(yes);

            void dummy(); //for GCC to supress -Wctor-dtor-privacy
        };

    }  // namespace detail

    // Basic is_container template; specialize to derive from std::true_type for all desired container types

    template <typename T>
    struct is_container : public std::integral_constant<bool,
                                                        detail::has_const_iterator<T>::value &&
                                                        detail::has_begin_end<T>::beg_value  &&
                                                        detail::has_begin_end<T>::end_value> { };

    template <typename T, std::size_t N>
    struct is_container<T[N]> : std::true_type { };

    template <std::size_t N>
    struct is_container<char[N]> : std::false_type { };

    template <typename T>
    struct is_container<std::valarray<T>> : std::true_type { };

    template <typename T1, typename T2>
    struct is_container<std::pair<T1, T2>> : std::true_type { };

    template <typename ...Args>
    struct is_container<std::tuple<Args...>> : std::true_type { };

}}  //namespace
#endif

For more explanation see my blog post.

Related question: c++ template class; function with arbitrary container type, how to define it?

Community
  • 1
  • 1
Shital Shah
  • 63,284
  • 17
  • 238
  • 185
0

Here is another c++14 solution inspired by previous answers. By defining enable_if_t, you can convert this to c++11 compatible code.

Tested on VS2019, and clang 6.0.

Checks implemented for type T:

  • has typename T::value_type
  • has typename T::iterator
  • has typename T::const_iterator
  • has typename T::size_type
  • T::begin() returns value of type T::iterator
  • T::end() returns value of type T::iterator
  • T::cbegin() returns value of type T::const_iterator
  • T::cend() returns value of type T::const_iterator
  • T::size() returns value of type T::size_type

Typename checks are implemented using void_t in the following way:

template<typename T, typename = void>
constexpr bool has_trait_xxx = false;
template<typename T>
constexpr bool has_trait_xxx<T, void_t<typename T::xxx>> = true;

Method return type checks are implemented using void_t + enable_if + is_same in the following way:

template<typename T, typename = void>
constexpr bool method_x_returns_type_y = false;
template<typename T>
constexpr bool method_x_returns_type_y<T, void_t<std::enable_if_t<is_same_v<decltype(std::declval<T>().x()), T::y>>>> = true;

Complete code for is_stl_container_like_v<T>:

template <typename... T>
using void_t = void;

template <typename T, typename U>
constexpr bool is_same_v = std::is_same<T, U>::value;

#define HAS_XXX_TRAIT_DEF(name)                                                                                    \
    template <typename T, typename = void>                                                                             \
    constexpr bool has_##name = false;                                                                                 \
    template <typename T>                                                                                              \
    constexpr bool has_##name<T, void_t<typename T::name>> = true;

HAS_XXX_TRAIT_DEF(value_type)
HAS_XXX_TRAIT_DEF(iterator)
HAS_XXX_TRAIT_DEF(const_iterator)
HAS_XXX_TRAIT_DEF(size_type)

template <typename T, typename = void>
constexpr bool is_iterable = false;
template <typename T>
constexpr bool
    is_iterable<T,
                void_t<std::enable_if_t<is_same_v<decltype(std::declval<T>().begin()), typename T::iterator>>,
                       std::enable_if_t<is_same_v<decltype(std::declval<T>().end()), typename T::iterator>>>> = true;

template <typename T, typename = void>
constexpr bool is_const_iterable = false;
template <typename T>
constexpr bool is_const_iterable<
    T,
    void_t<std::enable_if_t<is_same_v<decltype(std::declval<T>().cbegin()), typename T::const_iterator>>,
           std::enable_if_t<is_same_v<decltype(std::declval<T>().cend()), typename T::const_iterator>>>> = true;

template <typename T, typename = void>
constexpr bool is_sizeable = false;
template <typename T>
constexpr bool
    is_sizeable<T, void_t<std::enable_if_t<is_same_v<decltype(std::declval<T>().size()), typename T::size_type>>>> =
        true;

template <typename T>
constexpr bool is_stl_container_like_v = has_value_type<T> && has_iterator<T> && has_const_iterator<T> &&
                                         has_size_type<T> && is_iterable<T> && is_const_iterable<T> && is_sizeable<T>;


Example usage:

std::cout << is_stl_container_like_v<std::vector<int>> << "\n"; // 1
std::cout << is_stl_container_like_v<std::string> << "\n"; // 1
std::cout << is_stl_container_like_v<double> << "\n"; // 0

Problems with this implementation:

  1. references to container are not recognized as containers. e.g. std::vector<int>&
  2. does not check reference ,const_reference and difference_type typenames, and many other methods mentioned in named requirement Container

Thus, is_stl_container_like_v cannot be used to check whether a custom type meets the stl container requirement.

yangda
  • 111
  • 1
  • 5