How to write a type trait `is_container` or `is_vector`?
Asked Answered
T

11

41

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.

Torment answered 20/8, 2012 at 18:11 Comment(0)
N
49

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.

Neff answered 3/7, 2015 at 12:37 Comment(9)
is_cointainer_helper can be replaced by std::void_t nowadaysTeetotum
For both is_container structs, void is used as second parameter. After tests, it seems that it works whatever the type, if this is the same type : template<typename T, typename _ = int> and is_container_helper<...>, int>. Why so? Why doesn't it work if we use void and int, or int and void?Ability
Because you do use is_container< Type > which is is_container< Type, void > which is not specialised and default implementation is used: std::false_typeNeff
A Container might not be an AllocatorAwareContainer.Sprage
With use of std::void_t you can trash std::conditional_t?Putrid
Yeap, but when I wrote this answer, there was a bug in GCC which breaks this method (SFINAE did not work in std::void_t<...>: gcc.gnu.org/bugzilla/show_bug.cgi?id=64395)Neff
What is the , class _ = void part for?Brusa
how about valarray<int> and forward_list<int>?Shockey
@Shockey Check Container requirements. You may check any/all of them you need.Neff
T
24

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?

Torment answered 20/8, 2012 at 18:31 Comment(7)
+1... duh! (maybe you want to add the allocator_type there also, the trait as it is is really is_vector_with_default_allocatorMilkwhite
6 years after :) Would you care to post a link to some on line ide where you have placed a minimal usage example? ThanksXenon
@DusanJovanovic it is a type trait, you can look up any other type trait online and see how it is used. For example std::is_integralKennel
@Kennel this is wrong code. auto isv = is_vector<int>::value ; is explicit specialization of non-template struct 'is_vector' ... checkXenon
you need false general template first wandbox.org/permlink/76dOGKSuvEiBdAdwKennel
@Kennel exactly, this is why I asked to see the usage in the first place ...Xenon
@Kennel like hereXenon
L
20

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.

Litigable answered 20/8, 2012 at 18:26 Comment(11)
And for is_container? simply testing if it has an allocator_type?Nazi
@rhalbersma: The problem is what your definition of is_container is... Is my hand rolled single_list a container? is std::string a container? Does the fact that it has an allocator make an object a container?Milkwhite
I see your point. Writing out all STL requirements (all the standard iterator, value_type typedefs, all the member functions swap, begin, end, structors, operator ==, !=, etc. etc.) is torture. Bring on concepts!Nazi
for is_container, many people just look for begin and end functions, and use is_iterable, which is (most of the time) what you really needed to know anyway.Thimbleweed
@MooingDuck: Where do I find is_iterable?Torment
@Frank: Several random people have made is_iterable type traits, but they all do slightly different things, and nothing is standard. Sorry. I just meant, theoretically, if you're given an arbitrary container, you care less if it's actually a container, and you actually care if you can iterate over it's values, like with a boost::range of boost::counting_iteratorThimbleweed
@homemade-jam: not quite following the reason for the comment. enable_if is in the C++11 standard library, it is also available from Boost from a long time ago, and it is simple enough that I actually type it many times in small tests or if the project needs to support C++03 and boost is not a dependency...Milkwhite
It just seemed a little confusing to me when the second solution says "and in c++11". No criticism, just comment.Thaumaturge
@homemade-jam: :) in the question he is using boost::enable_if, so my assumption is that he is in a C++03 environment.Milkwhite
It is worth noting that the untested "using" syntax works only poorly in this case. It will try to force an instantiation the dependent type "T::value_type" which will fail with a compile error if T is something like int.Rademacher
This fails, with a complaint when T is not a struct, class or union type.Bonine
L
9

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.

Luthanen answered 20/8, 2012 at 22:0 Comment(0)
P
8

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.

Paisano answered 20/8, 2012 at 19:6 Comment(0)
T
4

The way I like to detect whether something is a container is to look for data() and size() member functions. Like this:

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

template <typename T>
struct is_container<T
   , std::void_t<decltype(std::declval<T>().data())
      , decltype(std::declval<T>().size())>> : std::true_type {};
Teetotum answered 20/12, 2017 at 20:41 Comment(3)
nice stunt. But this will "make" std::string (for example) into an container too . Using the above: is_container<std::string>::value is trueXenon
as it should! #17260095Teetotum
I was until now believing std::string is not an std container? Although, I agree with this text.Xenon
R
4

We can also use concepts. I compiled this with GCC 10.1 flag -std=c++20.


#include<concepts>

template<typename T>
concept is_container = requires (T a)
{ 
    a.begin(); 
    // Uncomment both lines for vectors only
    // a.data(); // arrays and vectors
    // a.reserve(1); // narrowed down to vectors
    
};

Risible answered 9/10, 2021 at 2:53 Comment(0)
H
3
template <typename T>
struct is_container {

    template <
       typename U,
       typename I = typename U::const_iterator
    >   
    static int8_t      test(U* u); 

    template <typename U>
    static int16_t     test(...);

    enum { value  =  sizeof test <typename std::remove_cv<T>::type> (0) == 1 };
};


template<typename T, size_t N>  
struct  is_container <std::array<T,N>>    : std::true_type { };
Hebrides answered 20/8, 2012 at 18:32 Comment(3)
says: error C2146: syntax error : missing ';' before identifier 'test' in visual studio 2012Cheri
VC doesn't know about uint8_tHebrides
#include <cstdint> will allow it.Provoke
X
3

Fast forward to 2018 and C++17, I was so daring to improve on @Frank answer

// clang++ prog.cc -Wall -Wextra -std=c++17

 #include <iostream>
 #include <vector>

 namespace dbj {
    template<class T>
      struct is_vector {
        using type = T ;
        constexpr static bool value = false;
   };

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

  // and the two "olbigatory" aliases
  template< typename T>
     inline constexpr bool is_vector_v = is_vector<T>::value ;

 template< typename T>
    using is_vector_t = typename is_vector<T>::type ;

 } // dbj

   int main()
{
   using namespace dbj;
     std::cout << std::boolalpha;
     std::cout << is_vector_v<std::vector<int>> << std::endl ;
     std::cout << is_vector_v<int> << std::endl ;
}   /*  Created 2018 by [email protected]  */

The "proof the pudding". There are better ways to do this, but this works for std::vector.

Xenon answered 27/6, 2018 at 8:34 Comment(0)
A
0

In our project we still didn't manage to migrate to compiler supporting C++11, so for type_traits of container objects I had to wrote a simple boost style helper:

template<typename Cont> struct is_std_container: boost::false_type {};
template<typename T, typename A> 
struct is_std_container<std::vector<T,A> >: boost::true_type {};
template<typename T, typename A> 
struct is_std_container<std::list<T,A> >: boost::true_type {};
template<typename T, typename A> 
struct is_std_container<std::deque<T,A> >: boost::true_type {};
template<typename K, typename C, typename A> 
struct is_std_container<std::set<K,C,A> >: boost::true_type {};
template<typename K, typename T, typename C, typename A> 
struct is_std_container<std::map<K,T,C,A> >: boost::true_type {};

template<typename Cont> struct is_std_sequence: boost::false_type {};
template<typename T, typename A> 
struct is_std_sequence<std::vector<T,A> >: boost::true_type {};
template<typename T, typename A> 
struct is_std_sequence<std::list<T,A> >: boost::true_type {};
template<typename T, typename A> 
struct is_std_sequence<std::deque<T,A> >: boost::true_type {};
Alcyone answered 30/6, 2017 at 14:13 Comment(1)
true_type can be implemented without boostLeporine
E
0

If you also want to make it work for const std::vector, you can use the following:

namespace local {

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

template<typename T>
struct isVector<T,
        typename std::enable_if<
                std::is_same<typename std::decay<T>::type, std::vector<typename std::decay<T>::type::value_type, typename std::decay<T>::type::allocator_type> >::value>::type> : std::true_type {
};

}

TEST(TypeTraitTest, testIsVector) {
    ASSERT_TRUE(local::isVector<std::vector<int>>::value);
    ASSERT_TRUE(local::isVector<const std::vector<int>>::value);

    ASSERT_FALSE(local::isVector<std::list<int>>::value);
    ASSERT_FALSE(local::isVector<int>::value);

    std::vector<uint8_t> output;
    std::vector<uint8_t> &output2 = output;
    EXPECT_TRUE(core::isVector<decltype(output)>::value);
    EXPECT_TRUE(core::isVector<decltype(output2)>::value);
}

Without the std::remove_cv call the second ASSERT_TRUE would fail. But of course this depends on your needs. The thing here is that according to the specs, std::is_same checks for const and volatile to also match.

Exospore answered 15/8, 2017 at 7:9 Comment(1)
added std::decay to support referencesExospore

© 2022 - 2024 — McMap. All rights reserved.