Определите, является ли тип контейнером STL во время компиляции

Я хотел бы написать шаблон, который будет определять, является ли тип контейнером stl во время компиляции.

У меня есть следующий фрагмент кода:

  struct is_cont{};
  struct not_cont{};

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

но я не уверен, как создать необходимые специализации для std::vector<T,Alloc>, deque<T,Alloc>, set<T,Alloc,Comp> так далее...

10 ответов

Решение

Во-первых, вы определяете свой основной шаблон, который будет иметь элемент false в случае по умолчанию:

template <typename T>
struct is_cont {
  static const bool value = false;
};

Затем вы определите частичные специализации для ваших типов контейнеров, которые вместо этого имеют значение true:

template <typename T,typename Alloc>
struct is_cont<std::vector<T,Alloc> > {
  static const bool value = true;
};

Затем для типа X, который вы хотите проверить, используйте его как

if (is_cont<X>::value) { ... } 

Примечание: следующий код взят из отличной утилиты pretty-print, написанной Kerrek SB ( тема об этом в stackru).

Отказ от ответственности: я не знаю, разрешено ли мне копировать и вставлять этот код здесь без разрешения оригинального автора. @Kerrek, дай мне знать, если у тебя возникнут проблемы. :-)


Вы можете использовать этот шаблон класса:

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

Использование:

 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

Обратите внимание, что is_container нужны следующие шаблоны классов помощников:

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

Многие из уже предложенных решений являются подробными для обнаружения контейнеров STL.
Они сосредоточены на характеристиках, которыми обладают все контейнеры, вместо того, чтобы явно указывать, что это за контейнеры.

Если вы хотите создать свои собственные контейнеры и проверить их на истинный тип, я бы порекомендовал другие решения. Если вы хотите проверять только допустимые контейнеры STL, а не контейнеры, подобные STL, рассмотрите возможность использования следующей реализации, поскольку она обеспечивает точное обнаружение контейнера STL:

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

Обратите внимание на использование std::decay чтобы избежать неправильного вывода типа на основе квалификаторов типа. Также мы использовали наследование std::true_type а также std::false_type чтобы не указывать ::type набирает себя. C++11 вариационные шаблоны используются для определения n параметров типа шаблона, необходимых для построения контейнеров.

Использование реализации - это то, что вы ожидаете:

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

печатает:

true
true
false

В соответствии с предположением, что универсальный тест времени компиляции для has-stl-container-like-interface был бы подходящим решением, этот тест определяет stl-подобный контейнер Tпо интерфейсу:

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 &

Дополнительные требования, например size() метод, может быть добавлен очевидным способом, или другие интерфейсы канонического типа, проверенные во время компиляции очевидным подобным способом.

#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

Вот тестовая программа, созданная с использованием 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
}

В С++20 вы можете использовать concept,

зависит от вас, чтобы добавить больше проверок из того, что вы считаете контейнером, но это может выглядеть так:

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

Пример использования .

Протестировано с 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;
    }
};

Применение:

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

Вывод:

1
0
0
1

Есть is_container в ускорении http://www.boost.org/doc/libs/1_51_0/libs/spirit/doc/html/spirit/advanced/customize/is_container.html

is_container<C>::type --- Результат метафункции, которая оценивается как mpl:: true_, если данный тип, C, должен рассматриваться как контейнер, mpl::false_ в противном случае. Обычно любая реализация is_container должна вести себя так, как если бы она была логическим MPL Constant..

Этот код определяет черты для контейнера. Это из библиотеки prettyprint:

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

Для получения дополнительной информации см. Мой пост в блоге.

Смежный вопрос: шаблонный класс C++; функция с произвольным типом контейнера, как его определить?

Вот еще одно решение на С++14, вдохновленное предыдущими ответами. Определивenable_if_tвы можете преобразовать это в код, совместимый с C++11.

Протестировано на VS2019 и clang 6.0.

Проверки реализованы для типаT:

  • имеет имя типаT::value_type
  • имеет имя типа
  • имеет имя типа
  • имеет имя типа
  • T::begin()возвращает значение типа
  • T::end()возвращает значение типаT::iterator
  • T::cbegin()возвращает значение типа
  • T::cend()возвращает значение типаT::const_iterator
  • T::size()возвращает значение типаT::size_type

Проверка имени типа реализуется следующим образом:

      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;

Проверки типа возвращаемого метода реализуются с помощьюvoid_t+enable_if+is_sameследующим образом:

      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;

Полный код для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>;


Пример использования:

      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

Проблемы с этой реализацией:

  1. ссылки на контейнер не распознаются как контейнеры. напримерstd::vector<int>&
  2. не проверяетreference,const_referenceиdifference_typeимена типов и многие другие методы, упомянутые в именованном контейнере требований.

Таким образом,is_stl_container_like_vне может использоваться для проверки того, соответствует ли пользовательский тип требованиям контейнера stl.

Проще говоря, с С++14 для STL и NON-STL:

      template<typename T>
constexpr bool is_container_v = std::is_same<decltype (std::declval<T>().begin()), decltype (std::declval<T>().end())>::value;
Другие вопросы по тегам