5

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

  • 那种类型是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

4 回答 4

8

编码高尔夫!这更短:

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特征可与仅接受类型参数的任何类模板重用。

于 2018-06-25T23:06:10.123 回答
7

您已经可以通过重用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 {};

不确定你能不能有更短的。

于 2018-06-25T22:58:57.053 回答
4

我不知道有Vecis_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>to func,模板#1 的约束将不被满足,所以模板#2 将被使用。但是如果你尝试传递 a std::vector<int>to func,会发生什么?答案是使用 your VecInt, 调用是模棱两可的,但使用 my VecInt, 使用模板 #1。

对于较旧的非约束函数模板,C++ 定义了一个“比”更专业的关系,该关系可以确定“函数模板是否X可以使用特定参数类型列表调用的许多情况,逻辑上意味着Y可以使用相同的参数调用函数模板”。这种关系的旧逻辑基于函数参数类型。例如,g(std::list<int>&)is more special than g(std::list<T>&)is more special than g(T&)is more special than g(const T&)。当有多个同名的函数模板时,这有助于 C++ 有时自然地“按我的意思行事”。

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

因此,C++ 仅定义了一些相当简单的规则,用于在两个模板的所有约束都满足的情况下比较约束。在不深入了解确切的技术细节的情况下,要记住的主要事项是:

  1. 在比较约束时,任何不是这四件事之一的表达式都被视为未知的布尔值:

    一个。概念专业。

    湾。一种形式的表达E1 && E2

    C。一种形式的表达E1 || E2

    d。一个表达式,它是围绕上述任何内容的一组(括号。)

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

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

因此,当比较你的Vecand时VecInt,C++ 知道mean和Vec<T>means ,但它停在那里并且不会尝试找到这两个表达式之间的任何逻辑关系。因此,两个概念都不包含另一个概念,并且使用这些概念的模板都没有比另一个更专业,这可能导致模棱两可的重载调用。is_vector<T>::valueVecInt<T>is_vector_of_int<T>::value

在比较 yourVec和 myVecInt时,C++ 不会尝试确定是什么std::is_same_v<typename T::value_type, int>意思。但是由于Vec<T> && anything真蕴涵Vec<T>也为真,C++ 确实知道VecIntsubsumes Vec,因此func#1 比func#2 更专业。

因此,尽管概念和约束肯定是该语言的一个受欢迎的有用补充,但我认为,让概念之间的包含关系尽可能强,将使适用于特定目的的概念与真正好的通用库级概念之间产生差异。做后者会很棘手(并且绝对需要一组通用的许多基本标准概念),我希望 C++ 社区需要学习一些“最佳实践”规则,了解如何一起做。

于 2018-06-25T23:51:06.533 回答
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> );
于 2018-06-25T23:10:11.810 回答