在编译时确定一个类型是否为STL容器

26
我希望能够编写一个模板,以便在编译时确定类型是否为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>等创建必要的特化。


6
我很好奇为什么一定需要使用标准库STL容器之一。如果其他容器符合这些属性,检查容器的属性是否合适,那么使用其他容器不就没问题了吗? - Nicol Bolas
2
std::unordered_map 在STL中原本不存在,但现在已经在C++11中加入。您认为它是STL类型吗? - MSalters
@MSalters:它确实符合容器要求。 - Billy ONeal
一旦您确定一个容器是STL容器,您会怎么做? - razeh
有点好奇,使用这样的模板会在什么场景或用例中发挥作用?因为任何使用向量的通用代码都无法使用关联容器,例如映射。如果您模板的用户只是为了迭代而询问,则非成员STL函数std :: begin()和std :: end()(cbegin / cend)主要用于此目的。 - Chenna V
如果你不是严格要求使用 STL 容器 而是一般的 可迭代对象,那么这里有一个答案:https://dev59.com/K2Yr5IYBdhLWcg3wQIC-#53967057 - aniliitb10
9个回答

31

注意:以下代码来自于一款名为pretty-print的优秀实用工具,作者是@Kerrek SB(关于它的一个话题在stackoverflow上)。

免责声明:我不知道是否可以在未经原作者许可的情况下在此处复制粘贴此代码。@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;
};

2
@bitmask:是的,因为它完全是通用的。:-) - Nawaz
1
@Nawaz:这是一个不错的方法,但它比所需的要多一点。 - Xander Tulip
在Visual Studio 2012中无法工作,始终返回false。 - Viktor Sehr
5
@ViktorSehr表示:“我不信任VS2012。” - Nawaz
@GabrieldeGrimouard:这个例子并不是为了使用“通用引用”(标准名称为“转发引用”)而设计的,但你可以根据需要进行修改。 - Nawaz
显示剩余5条评论

23

首先,您需要定义主模板。默认情况下,该模板将具有一个成员为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) { ... } 

4
我采用了相同的方法,问题在于STL容器有不止一个模板参数,例如Alloc、Comp等... - Xander Tulip
请注意,如果您向“vector”提供非默认参数,则此操作将失败。为了实现通用性,您应该转发所有模板参数。 - bitmask
2
真的 - 我让我的答案更加通用了。 - Vaughn Cato

19
许多已经提出的解决方案在检测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_typestd::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

不错,但无法在VS2015中编译 :( - kreuzerkrieg
你可能需要确保使用较新的语言标准进行构建。它在C++14中可以工作,如此处所示:http://ideone.com/PuD98p - Trevor Hickey
e:\documents\visual studio 2015\projects\consoleapplication6\consoleapplication6\consoleapplication6.cpp(23): 错误 C2976: 'std::array': 模板参数太少 c:\program files (x86)\microsoft visual studio 14.0\vc\include\tuple(687): 注意: 参见 'std::array' 的声明 e:\documents\visual studio 2015\projects\consoleapplication6\consoleapplication6\consoleapplication6.cpp(23): 错误 C3203: 'array': 未特化的类模板无法用作模板参数,需要实际类型 - kreuzerkrieg
e:\documents\visual studio 2015\projects\consoleapplication6\consoleapplication6\consoleapplication6.cpp(23):错误 C3211:'is_stl_container_impl::is_stl_container<int>':显式特化使用了部分特化语法,请改用模板<>。 e:\documents\visual studio 2015\projects\consoleapplication6\consoleapplication6\consoleapplication6.cpp(23):注意:此处声明了 'is_stl_container_impl::is_stl_container<int>'。 - kreuzerkrieg
1
is_stl_container<std::array<Args...>> 看起来不正确。 - Piotr Skotnicki
显示剩余3条评论

18

建议使用一种通用的编译时测验方法来测试是否存在类似于STL容器的接口,这个方法定义一个具有STL样式接口的T类型作为STL样式容器

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
}

或许有点晚了,但是 *pv 这个参数是做什么用的?因为我在返回语句中没有看到它被使用,而编译器(gcc 7.5.0)报错说它未被使用。 - Michiel uit het Broek

9
在C++20中,你可能会使用concept,具体加入哪些检查取决于你认为的容器,但它可能看起来像这样:
template <typename T>
concept Container = requires(T t)
{
    std::begin(t);
    std::end(t);
};

使用示例

标准库中已经存在的概念可能会引起您的兴趣,例如:

std::ranges::range


2

2
这看起来特别是 Boost Spirit 解析器库的内部内容。从文档中并不清楚它是否可以用作更一般的容器类型确定器,以及此特性的正面响应确切保证了该类型的什么内容!也许有人知道得更好... - andybuckley
Boost是为了与stdlib一起使用而制作的。毕竟,大多数新的stdlib东西都来自于Boost。除了查看代码之外,我不确定如何回应您的评论,但请记住,与其他解决方案相比,Boost已经经过审查和测试。 - kirill_igum
我并不是在批评Boost的正确性或其与STL的关系,而是指出它的这一部分并非显然用于一般用途。据我所知,Spirit解析器库及其依赖的模板元编程库并未计划纳入STL - Boost的某些部分比其他部分更“标准”。文档指出,这主要是为了供Spirit的>>和<<运算符重载使用。你检查过它是否适用于STL容器类型吗? - andybuckley

2

已使用 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

1
这段代码定义了容器的特征。它最初来自于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++ 模板类;如何定义具有任意容器类型的函数?


0

这是受之前答案启发的另一个C++14解决方案。通过定义enable_if_t,您可以将其转换为与C++11兼容的代码。

在VS2019和clang 6.0上进行了测试。

对类型T实现了以下检查:

  • 具有typename T::value_type
  • 具有typename T::iterator
  • 具有typename T::const_iterator
  • 具有typename T::size_type
  • T::begin()返回类型为T::iterator的值
  • T::end()返回类型为T::iterator的值
  • T::cbegin()返回类型为T::const_iterator的值
  • T::cend()返回类型为T::const_iterator的值
  • T::size()返回类型为T::size_type的值

使用void_t实现了typename检查,如下所示:

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. 不检查 referenceconst_referencedifference_type 等类型名称以及 named requirement Container 中提到的许多其他方法。

因此,is_stl_container_like_v 不能用于检查自定义类型是否满足 stl 容器的要求。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接