可以使用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();
  }
}