c++ 如何定义一个任意std::vector都能满足的概念?

tzdcorbm  于 2023-03-10  发布在  其他
关注(0)|答案(4)|浏览(165)

我希望有一个concept,需要一个任意向量作为返回类型:

template<typename T>
concept HasVector = requires (T t) {
    { T.vec() } -> std::same_as<std::vector<int>>; //works
    { T.vec() } -> std::same_as<std::vector<foo>>; //want to put something arbitrary in here
}

这样我们就得到了如下的结果:

class A {
std::vector<int> vec() { /* ... */}
}

class B {
std::vector<double> vec() { /* ... */}
}

static_assert(HasVector<A>);
static_assert(HasVector<B>);

此外,如果需要一个值类型满足其他概念的向量作为返回类型,那就更好了。

template<typename T>
concept Arithmetic = // as in the standard

template<typename T>
concept HasArithmeticVector = requires (T t ) {
    { T. vec() } -> std::same_as<std::vector<Arithmetic>>;

有没有这样一种方法把它放在概念的名称中呢?

v64noz0r

v64noz0r1#

我们首先编写一个变量模板来检查一个类型是否专用于一个模板:

template <typename T, template <typename...> class Z>
inline constexpr bool is_specialization_of = false;

template <template <typename...> class Z, class... Args>
inline constexpr bool is_specialization_of<Z<Args...>, Z> = true;

我们可以把它变成一个概念:

template <typename T, template <typename...> class Z>
concept Specializes = is_specialization_of<T, Z>;

然后我们可以用它来实现另一个概念:

template<typename T>
concept HasVector = requires (T t) {
    { t.vec() } -> Specializes<std::vector>;
};

如果您想做进一步的检查,那只是添加更多的需求。

template<typename T>
concept HasVector = requires (T t) {
    { t.vec() } -> Specializes<std::vector>;

    // or something along these lines
    requires Arithmetic<decay_t<decltype(t.vec()[0])>>;
    requires Arithmetic<range_value_t<decltype(t.vec())>>;
    // etc.
};
nnsrf1az

nnsrf1az2#

#include <concepts>
#include <vector>

template<typename T>
concept Arithmetic = std::integral<T> || std::floating_point<T>;

template<typename T>
concept HasVector = requires (T t) {
  []<Arithmetic U, typename A>(std::vector<U,A> const&){}(t.vec());
};

Demo.

wvmv3b1j

wvmv3b1j3#

虽然我确实喜欢巴里的方法,但我不喜欢该解决方案强加的比实际情况更通用(它不能支持接受任意数量的模板参数和非类型模板参数的模板)。不过,防止这种情况的一个简单方法是将概念定制为仅向量(例如SpecializesVector)。
另一种方法是利用std::vector的所有模板参数都可以通过它的公共成员类型value_typeallocator_type来访问的这一事实。

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

template<typename T>
concept vector = std::same_as<T, as_vector<T>>;

template<typename T>
concept has_vector = vector<decltype(std::declval<T>().vec())>;

如果我们希望向量的value_type是算术的,我们可以这样写:

template<typename T>
concept arithmetic = std::integral<T> or std::floating_point<T>;

template<typename T>
concept arithmetic_vector = vector<T> and arithmetic<typename T::value_type>;

template<typename T>
concept has_vector = arithmetic_vector<decltype(std::declval<T>().vec())>;

While I also think that 康桓瑋's solution looks very neat and concise, using templated lambda's inside of a requires-clause actually makes potential error messages really unclear and hard to understand (which is supposed to be one the strong points of concepts). For example, when checking a type that has a member-function vec() which returns a std::array instead of a std::vector , the error messsage produced by Clang would be something like:

<source>:37:1: error: static assertion failed
static_assert(has_vector<D>);
^             ~~~~~~~~~~~~~
<source>:37:15: note: because 'D' does not satisfy 'has_vector'
static_assert(has_vector<D>);
              ^
<source>:25:5: note: because '[]<arithmetic U, typename A>(std::vector<U, A>) {
}(t.vec())' would be invalid: no matching function for call to object of type
'(lambda at <source>:25:5)'
    []<arithmetic U, typename A>(std::vector<U, A>){}(t.vec());
    ^

相对于:

<source>:37:1: error: static assertion failed
static_assert(has_vector<D>);
^             ~~~~~~~~~~~~~
<source>:37:15: note: because 'D' does not satisfy 'has_vector'
static_assert(has_vector<D>);
              ^
<source>:21:22: note: because 'decltype(std::declval<D>().vec())'
(aka 'array<int, 3>') does not satisfy 'arithmetic_vector'
concept has_vector = arithmetic_vector<decltype(std::declval<T>().vec())>;
                     ^
<source>:18:29: note: because 'std::array<int, 3>' does not satisfy 'vector'
concept arithmetic_vector = vector<T> and arithmetic<typename T::value_type>;
                            ^
<source>:10:1: note: because substituted constraint expression is ill-formed:
no type named 'allocator_type' in 'std::array<int, 3>'

而且--在使用模板化lambda的概念时--当我们用vec()成员函数检查一个类型时,该成员函数返回一个没有算术value_typestd::vector,我们得到了与前面完全相同的错误消息:

<source>:36:1: error: static assertion failed
static_assert(has_vector<C>);
^             ~~~~~~~~~~~~~
<source>:36:15: note: because 'C' does not satisfy 'has_vector'
static_assert(has_vector<C>);
              ^
<source>:25:5: note: because '[]<arithmetic U, typename A>(std::vector<U, A>) {
}(t.vec())' would be invalid: no matching function for call to object of type
'(lambda at <source>:25:5)'
    []<arithmetic U, typename A>(std::vector<U, A>){}(t.vec());
    ^

相对于:

<source>:36:1: error: static assertion failed
static_assert(has_vector<C>);
^             ~~~~~~~~~~~~~
<source>:36:15: note: because 'C' does not satisfy 'has_vector'
static_assert(has_vector<C>);
              ^
<source>:21:22: note: because 'decltype(std::declval<C>().vec())'
(aka 'vector<char *>') does not satisfy 'arithmetic_vector'
concept has_vector = arithmetic_vector<decltype(std::declval<T>().vec())>;
                     ^
<source>:18:43: note: because 'typename vector<char *>::value_type'
(aka 'char *') does not satisfy 'arithmetic'
concept arithmetic_vector = vector<T> and arithmetic<typename T::value_type>;
                                          ^
<source>:7:22: note: because 'char *' does not satisfy 'integral'
concept arithmetic = std::integral<T> or std::floating_point<T>;
                     ^
/opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/concepts:100:24:
note: because 'is_integral_v<char *>' evaluated to false
    concept integral = is_integral_v<_Tp>;
                       ^
<source>:7:42: note: and 'char *' does not satisfy 'floating_point'
concept arithmetic = std::integral<T> or std::floating_point<T>;
                                         ^
/opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/concepts:109:30:
note: because 'is_floating_point_v<char *>' evaluated to false
    concept floating_point = is_floating_point_v<_Tp>;
                             ^
ny6fqffe

ny6fqffe4#

只要vec()是公共类方法:

template<typename T> struct is_vector { std::false_type operator()(); };

template<typename T, typename A> struct is_vector<std::vector<T, A>> {
    std::true_type operator()();
};

template<typename T>
concept HasVector = requires(T t) {
    { is_vector<decltype(t.vec())>{}() } -> std::same_as<std::true_type>;
};

class A {
public:
    std::vector<int> vec() {return {}; }
};

class B {
public:
    std::vector<double> vec() {return {}; }
};

class C {
};

static_assert(HasVector<A>);
static_assert(HasVector<B>);

相关问题