如何编写类型特征 `is_container` 或 `is_vector`?

39

您好!以下是您需要翻译的内容:

是否有可能编写一个类型特征,其对所有常见的STL结构(例如 vector, set, map等)的值都为true?

为了开始,我想编写一个类型特征,它对vector返回true,否则返回false。我尝试了这个方法,但它不能编译:

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

错误信息是模板参数在部分特化中未被使用:U
11个回答

49

看,另一个基于SFINAE的用于检测类似STL容器的解决方案:

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

当然,您可以更改要检查的方法和类型。
如果您想仅检测STL容器(即std::vectorstd::list等),您应该像这样做。
更新。正如@Deduplicator所指出的那样,容器可能不满足AllocatorAwareContainer要求(例如:std::array<T,N>)。这就是为什么在T :: allocator_type上进行检查不是必需的原因。但是,您可以以类似的方式检查任何/所有Container要求。

12
is_container_helper现在可以被std::void_t替代。 - Arvid
对于两个 is_container 结构体,都使用 void 作为第二个参数。经过测试,似乎无论类型如何,只要是相同类型:template<typename T, typename _ = int>is_container_helper<...>, int>,它就能正常工作。为什么?如果我们使用 voidintintvoid,为什么它不起作用? - Maxime Recuerda
2
通过使用 std::void_t,你可以摆脱 std::conditional_t 吗? - Marek R
是的,但当我写这个答案时,GCC存在一个错误,会破坏这种方法(SFINAE在std::void_t<...>中不起作用:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64395) - Nevermore
1
@martian 请查看容器要求。您可以检查任何/所有您需要的要求。 - Nevermore
显示剩余4条评论

22

实际上,经过一些试验和错误,我发现这很简单:

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

我仍然想知道如何编写更通用的is_container函数。我是否必须手动列出所有类型?


5
+1... 当然啦!(或许你想在那里加上 allocator_type,当前的特性实际上是 is_vector_with_default_allocator。) - David Rodríguez - dribeas
6年过去了 :) 你介意在哪个在线IDE上发布一个最小使用示例的链接吗?谢谢。 - user5560811
@DusanJovanovic 这是一种类型特征,你可以在网上查找任何其他类型特征并了解其用法。例如 std::is_integral。 - NoSenseEtAl
1
@NoSenseEtAl 这是错误的代码。auto isv = is_vector<int>::value; 是非模板结构体 'is_vector' 的显式特化...请检查。 - user5560811
1
你需要先获取错误的通用模板 https://wandbox.org/permlink/76dOGKSuvEiBdAdw - NoSenseEtAl
显示剩余2条评论

20
你会说这应该比那更简单...
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;
};

...但我不是很确定那是否更加简单。

在C++11中,您可以使用类型别名(我认为,未经测试):

template <typename T>
using is_vector = is_same<T, std::vector< typename T::value_type,
                                          typename T::allocator_type > >;

您的方法存在问题,因为在使用它的上下文中,类型 U 是不可推断的。

2
而对于 is_container 呢?只需测试它是否具有 allocator_type 即可? - TemplateRex
3
问题在于你对 is_container 的定义是什么...... 我自己编写的 single_list 是否算作容器?std::string 算不算容器?一个对象具有分配器属性,这是否意味着它是一个容器? - David Rodríguez - dribeas
1
对于 is_container,许多人只是寻找 beginend 函数,并使用 is_iterable,这通常是你真正需要知道的。 - Mooing Duck
1
@MooingDuck:我在哪里可以找到is_iterable - Frank
1
值得注意的是,在这种情况下,未经测试的“using”语法效果很差。它将尝试强制实例化依赖类型“T :: value_type”,如果T是像int这样的类型,则会因编译错误而失败。 - ltc
显示剩余6条评论

9
为什么不为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 { };
// ...

这样用户就能通过部分特化添加自己的容器。至于is_vector等,只需像我上面所做的那样使用部分特化,但将其限制为仅适用于一个容器类型,而不是多个。


8

虽然其他回答试图猜测一个类是否是容器,可能对你有用,但我想向你介绍另一种选择,即命名你想要返回true的类型。您可以使用此功能构建任意is_(something)特征类型。

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

等等。

您需要包含<type_traits>和您添加到规则的任何类。


4

我通常检测一个东西是否为容器的方法是查找 data()size() 成员函数。就像这样:

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

1
不错的技巧。但这将使std::string(例如)也成为一个容器。使用上述代码:is_container<std::string>::valuetrue - user5560811
3
C++中,std::string是一个容器吗? - Arvid
我之前一直认为std::string不是一个std容器?尽管如此,我同意这篇文章的观点。 - user5560811

3
快进到2018年和C++17,我很大胆地试图改进@Frank的答案。
// 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 dbj@dbj.org  */

"证明布丁的口感"。有更好的方法来做到这一点,但这对于std::vector有效。


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

在Visual Studio 2012中,错误C2146:标识符“test”之前缺少“;”的语法错误。 - Viktor Sehr
VC不知道uint8_t。 - Leonid Volnitsky
#include <cstdint> 将允许它。 - Beached

2

我们还可以使用概念。我使用GCC 10.1编译器并添加了-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
    
};


0
在我们的项目中,我们仍未成功迁移到支持 C++11 的编译器,因此对于容器对象的 type_traits,我不得不编写一个简单的 boost 风格帮助程序:
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 {};

true_type可以在不使用boost的情况下实现 - undefined

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