| title | 概念 |
|---|
未加类型限制的模板,类型错误到『实例化时 (instantiation time)』才会发现:
template<typename Seq, typename Val>
Val sum(Seq s, Val v) {
for (const auto& x : s)
v += x;
return v;
}假设已定义 Sequence 及 Value 两个 concepts,可定义类型受限的版本:
template<Sequence Seq, Value Val>
Val sum(Seq s, Val v) {
for (const auto& x : s)
v += x;
return v;
}template <Concept Type> 等价于 template<typename Type> requires Concept<Type>,其中 requires 开启「需求分句 (requires clause)」,其后的 Concept<Type> 为编译期谓词(Type 满足 Concept 则为 true)。
template<typename Seq, typename Val>
requires Sequence<Seq> && Value<Val>
Val sum(Seq s, Val v) {
for (const auto& x : s)
v += x;
return v;
}进一步要求 Seq 的元素的类型可以与 Val 类型进行算术运算:
template<Sequence Seq, Value Val>
requires Arithmetic<range_value_t<Seq>, Val>
Val sum(Seq s, Val n);
# 或更简洁的
template<Sequence Seq, Arithmetic<range_value_t<Seq>> Val>
Val sum(Seq s, Val n);template<forward_iterator Iter>
requires requires(Iter p, int i) { p[i]; p+i; } // 额外的需求
void advance(Iter p, int n) {
p += n; // ⚠️ 满足上述需求的 Iter,未必支持 +=
}其中第二个 requires 开启「需求表达式 (requires expression)」。后者为编译期谓词({} 中的代码合法则其值为 true),相当于泛型编程的汇编代码,只因出现在底层代码(如 concepts 的定义)中。
定义概念:
template<typename B>
concept Boolean = requires(B x, B y) {
{ x = true };
{ x = false };
{ x = (x == y) };
{ x = (x != y) };
{ x = !x };
{ x = (x = y) };
};
template<typename T>
concept EqualityComparable = requires (T a, T b) {
{ a == b } -> Boolean; // -> 之后必须跟某个 concept
{ a != b } -> Boolean;
};{ e } -> Concept 相当于 requires Concept<decltype((e))>,需考虑 decltype 引入的左值引用。
显式判断:
static_assert(EqualityComparable<int>); // 通过编译
struct S { int a; };
static_assert(EqualityComparable<S>); // 编译期报错隐式判断:
template<EqualityComparable T>
bool cmp(T a, T b) {
return a < b; // ⚠️ 未在 EqualityComparable 中检查
}
bool b0 = cmp(cout, cerr); // 未通过 EqualityComparable 检查
bool b1 = cmp(2, 3); // OK
bool b2 = cmp(2+3i, 3+4i); // 通过 EqualityComparable 检查,但在实例化时报错补全开头的例子:
#include <ranges>
#include <iterator>
template<typename S>
concept Sequence = requires (S a) {
typename range_value_t<S>; // S 必须有 value type
typename iterator_t<S>; // S 必须有 iterator type
requires input_iterator<iterator_t<S>>; // S 的 iterator 必须可读
requires same_as<range_value_t<S>, iter_value_t<S>>; // 类型必须一致
{ a.begin() } -> same_as<iterator_t<S>>; // S 必须有返回 iterator 的 begin()
{ a.end() } -> same_as<iterator_t<S>>;
};
template<typename T, typename U = T>
concept Numeric = requires(T x, U y) {
x+y; x-y; x*y; x/y; x+=y; x-=y; x*=y; x/=y; x=x; x=0;
};
template<typename T, typename U = T>
concept Arithmetic = Numeric<T, U> && Numeric<U, T>;💡 建议用形容词命名概念。
限制函数形参:
auto twice(Arithmetic auto x) { return x+x; } // 只支持算术类型
auto thrice(auto x) { return x+x+x; } // 支持任意可 + 类型
auto x1 = twice(7); // x1 == 14
auto s = string("Hello ");
auto x2 = twice(s); // string 不满足 Arithmetic
auto x3 = thrice(s); // x3 == "Hello Hello Hello "限制变量类型:
Channel open_channel(string);
auto ch1 = open_channel("foo"); // ch1 为 Channel 变量
Arithmetic auto ch2 = open_channel("foo"); // Channel 不满足 Arithmetic
ChannelLike auto ch3 = open_channel("foo"); // Channel 满足 ChannelLike限制返回类型:
Numeric auto some_function(int x) {
// ...
return fct(x); // an error unless fct(x) returns a Numeric
}标准库在 <concepts> 中定义了一些常用概念。
-
same_as<T, U>meansTis the same asU. -
derived_from<T, U>meansTis derived fromU. -
convertible_to<T, U>meansTis convertible toU. -
common_reference_with<T, U>meansTshares a common reference type withU. -
common_with<T, U>meansTshares a common type (common_type_t<T, U>) withU. -
integral<T>meansTis a type of integers. -
signed_integral<T>meansTis a type of signed integers. -
unsigned_integral<T>meansTis a type of unsigned integers. -
floating_point<T>meansTis a type of floating point numbers. -
assignable_from<T, U>meansTis assignable fromU. -
swappable_with<T, U>meansTis swappable withU.swappable<T>is short forswappable_with<T, T>.
boolean-testable(exposition-only) meansTcan be used in Boolean contexts.equality_comparable_with<T, U>meansTis equality comparable withU.equality_comparable<T>is short forequality_comparable_with<T, T>.
three_way_comparable_with<T, U, Cat = std::partial_ordering>means the three way comparison operator<=>onTandUoperands yield results consistent with the comparison category implied byCat.three_way_comparable<T, Cat = std::partial_ordering>is short forthree_way_comparable_with<T, T>.
totally_ordered_with<T, U>(defined in<compare>) means that all the 6 comparison operators on (possibly mixed)TandUoperands yield results consistent with a strict total order.totally_ordered<T>is short fortotally_ordered_with<T, T>.
标准库在 <compare> 中定义了三种 ordering 类型,用于表示三路比较运算符 <=> 的结果。它们都支持六种比较运算,区别在于:
partial_ordering表示相等的值不可替换(可以区分),且允许存在不可比较的值(即a < b,a == b,a > b可以都为false,如质点(按p.x < q.x && p.y < q.y定义p < q,相等时可按质量区分)。weak_ordering表示相等的值不可替换(可以区分),但不允许存在不可比较的值(即a < b,a == b,a > b有且仅有一个为true),如学生(按姓名字典序比较,相等时可学号区分)。strong_ordering表示相等的值可以替换(无法区分),且不允许存在不可比较的值,如整数、字符串。
-
destructible<T>meansTis destructible. -
constructible_from<T, Args>meansTcan be constructed from an argument list of typeArgs. -
default_initializable<T>meansTcan be default constructed. -
move_constructible<T>meansTcan be move constructed. -
copy_constructible<T>meansTcan be copy constructed. -
semiregular<T>meanscopyable<T>且default_initializable<T>.
-
invocable<F, Args>means anFcan be invoked with an argument list of typeArgs.-
regular_invocable<F, Args>means aninvocable<F, Args>that is equality-preserving (i.e.$\forall (x = y)\implies f(x)=f(y)$ , which (currently) cannot be represented in code).-
predicate<F, Args>means anregular_invocable<F, Args>that returns abool.-
relation<F, T, U>meanspredicate<F, T, U>.-
equivalence_relation<F, T, U>means anrelation<F, T, U>that provides an equivalence relation (, which (currently) cannot be represented in code). -
strict_weak_order<F, T, U>means anrelation<F, T, U>that provides strict weak ordering (, which (currently) cannot be represented in code).
-
-
-
-
indirectly_readablespecifies that a type is indirectly readable by applyingoperator *.indirectly_writablespecifies that a value can be written to an iterator's referenced object.weakly_incrementablespecifies that asemiregulartype can be incremented with pre- and post-increment operators.incrementablespecifies that the increment operation on aweakly_incrementabletype is equality-preserving and that the type isequality_comparable.
input_or_output_iteratorspecifies that objects of a type can be incremented and dereferenced.sentinel_for<S, I>specifies a typeSis a sentinel for aninput_or_output_iteratortypeI, i.e.semiregular<S>且input_or_output_iterator<I>且__WeaklyEqualityComparableWith<S, I>.sized_sentinel_for<S, I>specifies that the-operator can be applied to an iterator and a sentinel to calculate their difference in constant time.
input_iteratorspecifies that a type is an input iterator, that is, its referenced values can be read and it can be both pre- and post-incremented.forward_iteratorspecifies that aninput_iteratoris a forward iterator, supporting equality comparison and multi-pass.bidirectional_iteratorspecifies that aforward_iteratoris a bidirectional iterator, supporting movement backwards.random_access_iteratorspecifies that abidirectional_iteratoris a random-access iterator, supporting advancement in constant time and subscripting.contiguous_iteratorspecifies that arandom_access_iteratoris a contiguous iterator, referring to elements that are contiguous in memory.
output_iteratorspecifies that a type is an output iterator for a given value type, that is, values of that type can be written to it and it can be both pre- and post-incremented.
其中 sentinel 为 C++20 引入的概念,表示可直接与迭代器进行比较的类型,具体用法可参见示例:
#include <concepts>
#include <algorithm>
#include <iterator>
#include <iostream>
template<class Iter>
class Sentinel {
public:
Sentinel(int ee) : end(ee) { }
Sentinel() : end(0) {} // std::sentinel_for<S, I> 要求 S 有默认构造函数
friend bool operator==(const Iter& p, Sentinel s) { return (*p == s.end); }
friend bool operator!=(const Iter& p, Sentinel s) { return !(p == s); }
private:
std::iter_value_t<const char*> end; // the sentinel value
};
static_assert(std::sentinel_for<Sentinel<const char*>, const char*>);
int main() {
const char aa[] = "Hello, World!\nBye for now\n";
// 打印 "Hello, World!"
std::ranges::for_each(aa, Sentinel<const char*>('\n'),
[](const char x) { std::cout << x; });
}为简化算法对类型的限制,标准库定义了一组概念:
indirectly_movable<In, Out>specifies that values may be moved from anindirectly_readabletypeInto anindirectly_writabletypeOut.indirectly_movable_storable<In, Out>specifies thatindirectly_movable<In, Out>and that the move may be performed via an intermediate object.indirectly_copyable<In, Out>specifies that values may be copied from anindirectly_readabletypeInto anindirectly_writabletypeOut.indirectly_copyable_storable<In, Out>specifies thatindirectly_copyable<In, Out>and that the copy may be performed via an intermediate object.indirectly_swappable<I1, I2=I1>specifies that the values referenced by twoindirectly_readabletypes can be swapped.indirectly_comparable<I1, I2, Comp>specifies that the values referenced by twoindirectly_readabletypes can be compared.permutable<I>specifies the common requirements of algorithms that reorder elements in place, i.e.forward_iterator<I>且indirectly_movable_storable<I, I>且indirectly_swappable<I, I>.sortable<In, Comp = ranges::less, Proj = std::identity>specifies the common requirements of algorithms that permute sequences into ordered sequences. i.e.permutable<I>且indirect_strict_weak_order<Comp, std::projected<I, Proj>>.
mergeable<I1, I2, Out, Comp = ranges::less, Proj1 = std::identity, Proj2 = std::identity>specifies the requirements of algorithms that merge sorted sequences into an output sequence by copying elements, i.e.input_iterator<I1>且input_iterator<I2>且weakly_incrementable<Out>且indirectly_copyable<I1, Out>且indirectly_copyable<I2, Out>且indirect_strict_weak_order<Comp, std::projected<I1, Proj1>, std::projected<I2, Proj2>>.
其中
-
Proj表示作用在Iter型迭代器所指类型(可由std::iter_reference_t<Iter>得到)上的可调用类型(返回值类型可由std::indirect_result_t<Proj&, Iter>得到),其名称来源于数学中的「投影 (projection)」,例如:// 按绝对值由大到小排序并打印 auto v = std::vector<int>{ -1, +2, -3 }; std::ranges::sort(v, /* Comp */std::ranges::greater{}, /* a temporary Proj */[](int i){ return std::abs(i); }); std::ranges::for_each(v, /* another Proj */[](int i){ std::cout << i << ' '; });
-
通常不需要实施上述投影,故标准库提供了默认投影类型
std::identity(defined in<functional>),其operator()原样返回实参。 -
std::projected(defined in<iterator>) 用于提取Proj型可调用对象(作用于In型迭代器所指对象)的返回值类型:template< std::indirectly_readable In, std::indirectly_regular_unary_invocable<In> Proj > struct projected { using value_type = std::remove_cvref_t<std::indirect_result_t<Proj&, In>>; std::indirect_result_t<Proj&, In> operator*() const; // 只声明不定义 };
Range 是对容器概念的推广,可以由「起始迭代器 + END」来定义,其中 END 可以是:
- 终止迭代器,如
{ vec.begin(), vec.end() } - 个数,如
{ vec.begin(), vec.size() } - 终止条件,如
{ vec.begin(), [](int x){ return x % 2; } }
标准库在命名空间 std::ranges 中定义了一些常用的 range concepts:
Range concepts |
说明 |
|---|---|
ranges::range |
提供「起始迭代器 + END」 |
ranges::borrowed_range |
迭代器可返回(不会空悬) |
ranges::sized_range |
支持 O(1) size() |
ranges::view |
支持 O(1) operator=() |
ranges::input_range |
支持 input_iterator |
ranges::output_range |
支持 output_iterator |
ranges::forward_range |
支持 forward_iterator |
ranges::bidirectional_range |
支持 bidirectional_iterator |
ranges::random_access_range |
支持 random_access_iterator |
ranges::contiguous_range |
支持 contiguous_iterator |
ranges::common_range |
|
ranges::viewable_range |
可以安全地转化为 view |
ranges::constant_range (C++23) |
元素只读 |
标准库在命名空间 std::ranges 中还提供了一些对常用算法的封装,使得形如 std::sort(v.begin(), v.end()) 的调用可简化为 std::ranges::sort(v),从而避免传错迭代器。
View 是对 range 的轻量化封装(适配器)。
标准库在命名空间 std::ranges 中提供了一些常用的 views:
VIEW |
for (auto x : VIEW) { use(x); } 的传统写法 |
|---|---|
all_view{r} |
for (auto x : r) use(x); |
filter_view{r, p} |
for (auto x : r) if (p(x)) use(x); |
transform_view{r, f} |
for (auto x : r) use(f(x)); |
take_view{r, n} |
int i{0}; for (auto x : r) if (i++ == n) break; else use(x); |
drop_view{r, n} |
int i{0}; for (auto x : r) if (i++ < n) continue; else use(x); |
take_while_view{r, p} |
for (auto x : r) if (!p(x)) break; else use(x); |
drop_while_view{r, p} |
for (auto x : r) if (p(x)) continue; else use(x); |
join_view{r} |
for (auto &y : r) for (auto x : y) use(x); |
keys_view{r} |
for (auto [x, y] : r) use(x); |
values_view{r} |
for (auto [y, x] : r) use(x); |
ref_view{r} |
for (auto &x : r) use(x); |
| 以下为生成器 | |
iota_view{y} |
for (int i = 0: true; ++i) use(y + i); |
iota_view{y, z} |
for (auto x = y: x < z; ++x) use(x); |
istream_view<double>{cin} |
double x; while (cin >> x) use(x); |
表中 ranges::X_view{ARGS} 等价于 views::X(ARGS),即每个 views::X 函数生成一个 ranges::X_view 对象。
例如按条件过滤:
auto filter_odd(ranges::forward_range auto& r) {
ranges::filter_view v {r, [](int x) { return x % 2; } }; // v 的用户只访问 r 中的奇数
return v; // 轻量化封装,直接按值返回
}
int main() {
auto v = vector<int>{ 3, 5, 1, 2 };
cout << "odd numbers: ";
auto fv = filter_odd(v);
static_assert(ranges::forward_range<decltype(fv)>); // view 依然是 range
ranges::for_each(fv, [](int x) { cout << x << ' '; }); // 可以像 range 一样使用 view
cout << "\n";
}可以创建 view of view,例如:
ranges::forward_range/* 类型限制 */ auto
take_2(ranges::view auto/* view 无需传引用 */ fv) {
ranges::take_view tv {fv, 100}; // 只访问前 2 个奇数
// 等价于 auto tv = views::take(fv, 100);
return tv;
}
int main() {
auto v = vector<int>{ 3, 5, 1, 2 };
cout << "odd numbers: ";
auto fv = filter_odd(v);
auto tv = take_2(fv); // view of view
ranges::for_each(tv, [](int x) { cout << x << ' '; });
cout << "\n";
}标准库 range 及 view 支持 | 运算符,可以像在 Unix shell 中串联多个命令一样,串联多个 filters:
void user(ranges::forward_range auto& r) {
auto odd = [](int x) { return x % 2; };
for (int x : r | views::filter(odd) | views::take(3)) {
cout << x << ' ';
}
}
// 等价于
void user_pre20(ranges::forward_range auto& r) {
auto odd = [](int x) { return x % 2; };
int cnt = 0;
for (int x : r) {
if (odd(x)) {
cout << x << ' ';
if (++cnt == 3) {
break;
}
}
}
}遍历嵌套数据结构:
#include <iostream>
#include <map>
#include <ranges>
int main() {
auto name_to_game_to_score = std::map<std::string, std::map<std::string, int>>();
name_to_game_to_score["Bob"] = {
{"football", 10}, {"basketball", 20},
};
name_to_game_to_score["Alice"] = {
{"football", 30}, {"basketball", 40}, {"volleyball", 50},
};
auto range_of_score = name_to_game_to_score
| std::views::values // range of std::map<std::string, int>
| std::views::join // range of std::pair<std::string, int>
| std::views::values; // range of int
for (int score : range_of_score) {
std::cout << score << ' ';
}
std::cout << '\n'; // print 40 30 50 20 10
auto range_of_score_ptr = range_of_score
| std::views::transform([](int &i){ return &i; });
for (int *score_ptr : range_of_score_ptr) {
*score_ptr *= 10;
}
for (int score : range_of_score) {
std::cout << score << ' ';
}
std::cout << '\n'; // print 400 300 500 200 100
auto range_of_score_ref = range_of_score_ptr
| std::views::transform([](int *i) -> int & { return *i; });
for (int &score_ref : range_of_score_ref) {
score_ref /= 10;
}
for (int score : range_of_score) {
std::cout << score << ' ';
}
std::cout << '\n'; // print 40 30 50 20 10
}