可以使用concept
的位置
下图展示了模板声明中所有可以使用concept
的位置
注意,在requires-clause子句中可以使用type traits或concept,而其余位置(type-constrained, constrained placeholder type)中只能使用concept。
requires-expression
标准提供了requires-expression
结合concept来实现各种各样的约束。一个requires-expression
包括parameter list和body两个部分。
requires-expression
的body部分可以有以下四种类型的约束:
- Simple requirement (SR): checks for whether a certain statement is valid.
- Nested requirement (NR): a way to start a new requires-clause inside a requires-expression.
- Compound requirement (CR): check the return type of an expression and the noexceptness of that expression
- Type requirement (TR): asserts that a certain type is valid.
下面是一些具体例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
template<typename T, typename U>
concept same_as = std::is_same_v<std::remove_cvref_t<T>,
std::remove_cvref_t<U>>;
template<typename... Args>
concept Addable = requires(Args... args)
{
(... + args); // SR
requires are_same_v<Args...>; // NR
requires sizeof...(Args) > 1; // NR
{ (... + args) } noexcept->same_as<first_arg_t<Args...>>;
// CR, `same_as` must be a concept,
// the compiler will injects the resulting type from the compound statement as the first parameter of `same_as`
};
template<typename... Args>
requires Addable<Args...>
auto Add(Args&&... args)
{
return (... + args);
}
template<typename T>
concept containerTypes = requires(T t)
{ // TR
typename T::value_type;
typename T::size_type;
typename T::allocator_type;
typename T::iterator;
typename T::const_iterator;
};
|
定义concept
我们可以通过其他concepthe和requires-expression
来定义concept
。一个concept
的组成部分如下图所示
同一般的模板一样,在模板参数列表中,我们可以使用类型,非类型模板参数(NTTPs)或concept
来声明一个模板参数。
测试concept
concept
总是在编译期产生一个bool
值,于是我们可以通过static_assert
来测试一个concept
。
1
2
3
|
static_assert(not Addable<int, double>);
static_assert(not Addable<int>);
static_assert(Addable<int, int>);
|
在concept
中使用constexpr
函数
All parameters we define in the optional parameter list of the requires-expression should only appear as unevaluated operands.
1
2
|
requires(T t) { requires t.size() == N; }; // not allowed, t is evaluated.
// can be subsituted with constexpr function
|
the function does not use a parameter created in a requires expression. eg.
1
2
3
4
5
6
7
8
9
10
11
12
|
// #A A helper function with a default parameter
constexpr auto GetSize(const auto& t = {})
{
return t.size(); // #B Call the size function of the container
}
// #C A concept which uses T.size
template<typename T, size_t N>
concept SizeCheck = (GetSize<T>() == N);
// #D A function that uses SizeCheck
void SendOnePing(const SizeCheck<1> auto& cont);
|
一些concept
的应用
concept可用于限制类型推导的范围
1
|
const std::integral auto size = v.size(); // limit the type's properties
|
使用concept与constexpr if
可以替代std::enable_if
,来根据条件调用函数。如
1
2
3
4
5
6
7
8
9
10
11
12
13
|
template<typename T>
concept SupportsValidation = requires(T t)
{
t.validate();
};
template<typename T>
void Send(const T& data)
{
if constexpr(SupportsValidation<T>) { data.validate(); }
// actual code sending the data
}
|
当然,下面的形式也是可行的
1
2
3
4
5
6
7
|
template<typename T>
void Send(const T& data)
{
if constexpr(requires(T t) {t.validate();}) { data.validate(); }
// actual code sending the data
}
|
使用尾置require-clause可以使得根据类型的concept约束来选择不同的函数执行,包括构造函数和析构函数。在进行”重载决议时“采取从约束最强到约束最弱的函数一次进行匹配。下面是一个例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
template<typename T>
concept NotTriviallyDestructible =
not std::is_trivially_destructible_v<T>;
template<typename T>
class optional {
public:
optional() = default;
// #A Only if not trivially destructible
~optional() requires NotTriviallyDestructible<T>;
// #B If not trivially destructible and has Release method
~optional() requires NotTriviallyDestructible<T> and
HasRelease<T>;
// #C Trivial and has Release method
~optional() requires HasRelease<T>
{
if(has_value) { value.as()->Release(); }
}
~optional() = default;
optional(
const optional&) requires std::is_copy_constructible_v<T>
= default;
private:
union storage_t {
using aligned_storage_t =
std::aligned_storage_t<sizeof(T), alignof(T)>;
aligned_storage_t data;
storage_t() = default;
T* as() { return reinterpret_cast<T*>(&data); }
// use placement new to create an instance of T inside this
// union
};
storage_t value;
static_assert(std::is_copy_constructible_v<storage_t>);
static_assert(std::is_trivially_destructible_v<storage_t>);
bool has_value{true};
};
template<typename T>
optional<T>::~optional() requires NotTriviallyDestructible<T>
{
if(has_value) { value.as()->~T(); }
}
template<typename T>
optional<T>::~optional() requires NotTriviallyDestructible<T> &&
HasRelease<T>
{
if(has_value) {
value.as()->Release();
value.as()->~T();
}
}
|