惯用的方式写概念,说该类型是std :: vector

时间:2018-06-25 22:39:59

标签: c++ typetraits c++-concepts c++20

我有以下代码实现以下类型特征:

  • 类型为std::vector
  • 该类型为std::vector的整数

它可以工作,但是很冗长。
有没有使用概念写这个的更短/更巧妙的方法?
我知道我可以从range-v3或其他类似的库中窃取概念,但让我们假设我想自己实现它。

#include <iostream>
#include <string>
#include <type_traits>
#include <vector>

template <class T>
struct is_vector {
  static constexpr bool value = false;
};
template <class T, class A>
struct is_vector<std::vector<T, A> > {
  static constexpr bool value = true;
};

template <class T>
struct is_vector_of_int {
  static constexpr bool value = false;
};

template <class A>
struct is_vector_of_int<std::vector<int, A> > {
  static constexpr bool value = true;
};

// TODO add _v bool

template<typename T>
concept bool Vec = is_vector<T>::value;

template<typename T>
concept bool VecInt = is_vector_of_int<T>::value;

struct my_allocator : public std::allocator<int>{
};

template<VecInt V>
size_t func (const V& v){
    return v.size();
}
int main()
{
    static_assert(!is_vector<std::string>::value);
    static_assert(is_vector<std::vector<int, my_allocator>>::value);
    static_assert(is_vector<std::vector<int, std::allocator<int>>>::value);

    static_assert(!is_vector_of_int<std::string>::value);
    static_assert(is_vector_of_int<std::vector<int, my_allocator>>::value);
    static_assert(!is_vector_of_int<std::vector<float, my_allocator>>::value);

    static_assert(Vec<std::vector<float, my_allocator>>);
    static_assert(!VecInt<std::vector<float, my_allocator>>);
    static_assert(Vec<std::vector<int>>);
    std::vector<float> vf{1.1,2.2,3.3};
    std::vector<int> vi{1,2,3};
    // std::cout << func (vf);
    std::cout << func (vi);
}

4 个答案:

答案 0 :(得分:5)

通过重新使用std::true_type / std::false_type,您已经可以使用较短的代码:

template <class T>
struct is_vector : std::false_type {};

template <class T, class A>
struct is_vector<std::vector<T, A>> : std::true_type {};

template <class T>
struct is_vector_of_int : std::false_type {};

template <class A>
struct is_vector_of_int<std::vector<int, A>> : std::true_type {};

不确定您可以将其缩短。

答案 1 :(得分:4)

打高尔夫球!这更短:

template<class, template<class...> class>
inline constexpr bool is_specialization = false;
template<template<class...> class T, class... Args>
inline constexpr bool is_specialization<T<Args...>, T> = true;

template<class T>
concept bool Vec = is_specialization<T, std::vector>;

template<class T>
concept bool VecInt = Vec<T> && 
  std::is_same_v<int, typename T::value_type>;

具有预期的行为(https://wandbox.org/permlink/iZpUZRC5s73co0bV),并且is_specialization特性可以在仅接受类型参数的任何类模板中重复使用。

答案 2 :(得分:3)

我不知道有什么比Vec专长更好的写概念is_vector的方法。但是VecInt可以简化为:

template <typename T>
concept bool VecInt =
    Vec<T> && std::is_same_v<typename T::value_type, int>;

(顺便说一句,可用的实验性g ++概念支持基于比C ++ 20接受的提议更旧的提议。因此,尽管当前的g ++ -fconcepts需要concept bool VecInt = ...,但C ++ 20将改为需要concept VecInt = ...,删除bool部分。当然,概念专门化的类型始终为bool,因此在那里没有必要。)

这也带来了另一个改进。假设您有两个重载,而不仅仅是一个函数模板func

template <VecInt V> std::size_t func(const V&);  // #1
template <Vec V> std::size_t func(const V&);     // #2

如果您尝试将std::vector<double>传递给func,将无法满足模板1的约束,因此将使用模板2。但是,如果您尝试将std::vector<int>传递给func,会发生什么?答案是,使用您的VecInt时,通话是不明确的,但是使用我的VecInt,则使用了模板#1。

使用较早的非约束函数模板,C ++定义了“比...更专业”的关系,该关系可以确定是否可以通过一定参数类型列表调用“函数模板X”的许多情况,从逻辑上暗示该函数模板Y可以使用相同的参数来调用”。此关系的较早逻辑基于功能参数类型。例如,g(std::list<int>&)g(std::list<T>&)更专业,比g(T&)g(const T&)更专业。当有多个具有相同名称的函数模板时,这可以帮助C ++有时自然地“执行我的意思”。

如该示例所示,有时满足一个概念或约束在逻辑上意味着满足另一个概念或约束,如果这意味着C ++可以使用此事实为函数模板重载(和类模板)定义“比……更专业”,那就太好了。部分专业化)。但是模板约束比参数类型要复杂得多,并且从中确定逻辑含义通常要困难得多。

因此,在满足两个模板的所有约束的情况下,C ++仅定义了一些比较简单的规则来比较约束。在不了解确切的技术细节的情况下,要记住的主要内容是:

  1. 在比较约束时,任何不是以下四项之一的表达式将被视为未知布尔值:

    a。概念专长。

    b。形式为E1 && E2的表达式。

    c。形式为E1 || E2的表达式。

    d。上面的任何一个表达式都是由(个括号)组组成的表达式。

  2. 在比较约束时,即使它们使用完全相同的标记拼写,但不在上述类别中的两个表达式也不会被视为等效。 (但是,如果某个概念的定义中的同一表达式通过概念定义的“扩展”以多种方式使用,则必须始终将该表达式视为具有一致的逻辑值。)

如果使用这些简化的规则,则有可能表明(例如,使用真值表)满足约束条件X意味着也必须满足约束条件Y,我们说X“包含” Y。并且,如果两个约束函数模板或类模板的部分专业化等效于忽略它们的约束,并且模板#1的组合约束包含模板#2的组合约束(但反之则不行),这是使用模板的另一种方式#1被认为比“模板2”更专业。

因此,在比较您的VecVecInt时,C ++知道Vec<T>的意思是is_vector<T>::value,而VecInt<T>的意思是is_vector_of_int<T>::value,但是到此为止并且不会尝试在这两个表达式之间找到任何逻辑关系。因此,这两个概念都不会包含另一个概念,并且使用这些概念的模板都不比另一个概念更专业,这可能会导致模棱两可的重载调用。

在比较您的Vec和我的VecInt时,C ++不会尝试确定std::is_same_v<typename T::value_type, int>的含义。但是由于Vec<T> && anything为true意味着Vec<T>也为true,因此C ++确实知道VecInt包含Vec,因此func#1比{{1 }}#2。

因此,尽管概念和约束无疑是对该语言的一种受欢迎的有用补充,但我认为,要尽可能强地保持概念之间的包含关系,将使对于特定目的和真正好的通用库而言足够有效的概念之间的区别变得如此-年级的概念。进行后者将很棘手(并且绝对需要许多基本标准概念的通用集合),而且我希望C ++社区将需要学习一些“最佳实践”规则,以了解如何一起进行。

答案 3 :(得分:2)

template<class T>
struct tag_t{using type=T;};
template<class T>
constexpr tag_t<T> tag{};
template<template<class...>class Z>
struct ztemplate_t{
  template<class...Ts>
  constexpr auto operator()(tag_t<Ts>...)const{ return tag<Z<Ts...>>; } // does not work in some modern compilers
};
template<template<class...>class Z>
constexpr ztemplate_t<Z> ztemplate{};

template<class...Ts>
constexpr std::false_type is(Ts...){return {};}
template<class...Us, template<class...>class, class...Ts>
constexpr std::true_type is( tag_t<Z<Ts...,Us...>>, ztemplate_t<Z>, tag_t<Ts>... ){ return {};}

好样板完成。

template<class T>
constexpr auto is_vector = is( tag<T>, ztemplate<std::vector> );
template<class T>
constexpr auto is_vector_int = is( tag<T>, ztemplate<std::vector>, tag<int> );