You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
template <typename Iter, typename Callable>
voidforeach (Iter current, Iter end, Callable op) {
while (current != end) { // as long as not reached the endop(*current); // call passed operator for current element
++current; // and move iterator to next element
}
}
使用不同的Function Objects来调用这个模板:
// a function to call:voidfunc(int i) { std::cout << "func() called for: " << i << '\n'; }
// a function object type (for objects that can be used as functions):classFuncObj {
public:voidoperator()(int i) const { // Note: const member function
std::cout << "FuncObj::op() called for: " << i << '\n';
}
};
intmain(int argc, constchar **argv) {
std::vector<int> primes = {2, 3, 5, 7, 11, 13, 17, 19};
foreach (primes.begin(), primes.end(), func); // range function as callable (decays to pointer)foreach (primes.begin(), primes.end(), &func); // range function pointer as callableforeach (primes.begin(), primes.end(), FuncObj()); // range function object as callableforeach (primes.begin(), primes.end(), // range lambda as callable
[](int i) {
std::cout << "lambda called for: " << i << '\n';
});
return0;
}
template <typename Iter, typename Callable, typename... Args>
voidforeach (Iter current, Iter end, Callable op, Args const &... args) {
while (current != end) { // as long as not reached the end of the elementsstd::invoke(op, // call passed callable with
args..., // any additional args
*current); // and the current element
++current;
}
}
// a class with a member function that shall be calledclassMyClass {
public:voidmemfunc(int i) const {
std::cout << "MyClass::memfunc() called for: " << i << '\n';
}
};
intmain() {
std::vector<int> primes = {2, 3, 5, 7, 11, 13, 17, 19};
// pass lambda as callable and an additional argument:foreach (
primes.begin(), primes.end(), // elements for 2nd arg of lambda
[](std::string const &prefix, int i) { // lambda to call
std::cout << prefix << i << '\n';
},
"- value: "); // 1st arg of lambda// call obj.memfunc() for/with each elements in primes passed as argument
MyClass obj;
foreach (primes.begin(), primes.end(), // elements used as args
&MyClass::memfunc, // member function to call
obj); // object to call memfunc() for
}
#include<type_traits>template <typename T>
classC {
// ensure that T is not void (ignoring const or volatile):static_assert(!std::is_same_v<std::remove_cv_t<T>, void>,
"invalid instantiation of class C for void type");
public:template <typename V> voidf(V &&v) {
ifconstexpr (std::is_reference_v<T>) {
... // special code if T is a reference type
}
ifconstexpr (std::is_convertible_v<std::decay_t<V>, T>) {
... // special code if V is convertible to T
}
ifconstexpr (std::has_virtual_destructor_v<V>) {
... // special code if V has virtual destructor
}
}
};
template<typename T>
voidf (T&& x) {
auto p = &x; // might fail with overloaded operator &auto q = std::addressof(x); // works even with overloaded operator &
...
}
#include<utility>
#include<iostream>structDefault { intfoo() const { return1; } };
structNonDefault
{
NonDefault() = delete;
intfoo() const { return1; }
};
intmain()
{
decltype(Default().foo()) n1 = 1; // type of n1 is int// decltype(NonDefault().foo()) n2 = n1; // error: no default constructordecltype(std::declval<NonDefault>().foo()) n2 = n1; // type of n2 is int
std::cout << "n1 = " << n1 << '\n'
<< "n2 = " << n2 << '\n';
}
完美转发 Perfect Forwarding
template<typename T>
voidf (T&& t) // t is forwarding reference {
g(std::forward<T>(t)); // perfectly forward passed argument t to g()
}
或者转发临时变量,避免无关的拷贝开销:
template<typename T>
voidfoo(T x) {
auto&& val = get(x);
...
// perfectly forward the return value of get() to set():set(std::forward<decltype(val)>(val));
}
classC; // C is an incomplete type
C const* cp; // cp is a pointer to an incomplete typeextern C elems[10]; // elems has an incomplete typeexternint arr[]; // arr has an incomplete type
...
classC { }; // C now is a complete type (and therefore cpand elems no longer refer to an incomplete type)int arr[10]; // arr now has a complete type
Callables
许多基础库都要求调用方传递一个可调用的实体(entity)。例如:一个描述如何排序的函数、一个如何hash的函数。一般用
callback
来描述这种用法。在C++中有以下几种形式可以实现callback,它们都可以被当做函数参数传递并可以直接使用类似f(...)
的方式调用:operator()
的类(有时被叫做functors
),包括lambdas.C++使用
callable type
来描述上面这些类型。比如,一个可以被调用的对象称作callable object
,我们使用callback
来简化这个称呼。编写泛型代码会因为这个用法的存在而可扩展很多。
函数对象 Function Objects
例如一个for_each的实现:
使用不同的
Function Objects
来调用这个模板:解释一下:
foreach (primes.begin(), primes.end(), func);
按照值传递时,传递函数会decay为一个函数指针。foreach (primes.begin(), primes.end(), &func);
这个比较直接,直接传递了一个函数指针。foreach (primes.begin(), primes.end(), FuncObj());
这个是上面说过的functor
,一个重载了operator()
的类。所以,当调用op(*current);
时,实际是在调用op.operator()(*current);
. ps. 如果不加函数声明后面的const,在某些编译器中可能会报错。处理成员函数及额外的参数
上面没有提到一个场景 : 成员函数。因为调用非静态成员函数的方式是
object.memfunc(. . . )
或ptr->memfunc(. . . )
,不是统一的function-object(. . . )
。std::invoke<>()
幸运的是,从C++17起,C++提供了
std::invoke<>()
来统一所有的callback形式:那么,
std::invoke<>()
是怎么统一所有callback形式的呢?注意,我们在foreach中添加了第三个参数:
Args const &... args
. invoke是这么处理的:使用:
注意在callback是成员函数的情况下,是如何调用foreach的。
统一包装
std::invoke()
的一个场景用法是:包装一个函数调用,这个函数可以用来记录函数调用日志、测量时间等。一个需要考虑的事情是,如何处理op的返回值并返回给调用者:
这里使用
decltype(auto)
(从C++14起)(decltype(auto)
的用法可以看之前的文章 : c++11-17 模板核心知识(九)—— 理解decltype与decltype(auto))如果想对返回值做处理,可以声明返回值为
decltype(auto)
:但是有个问题,使用
decltype(auto)
声明变量,值不允许为void,可以针对void和非void分别进行处理:std::invoke_result<>
只有从C++17起才能使用,C++17之前只能用typename std::result_of<Callable(Args...)>::type
.泛型库的其他基本技术
Type Traits
这个技术很多人应该很熟悉,这里不细说了。
这里,我们使用type_traits来进行不同的实现。
std::addressof()
可以使用
std::addressof<>()
获取对象或者函数真实的地址, 即使它重载了operator &
. 不过这种情况不是很常见。当你想获取任意类型的真实地址时,推荐使用std::addressof<>():
比如在STL vector中,当vector需要扩容时,迁移新旧vector元素的代码:
这里使用
std::addressof()
获取新vector当前元素的地址,然后进行copy(或move)。可以看之前写的c++ 从vector扩容看noexcept应用场景std::declval
std::declval
可以被视为某一特定类型对象引用的占位符。它不会创建对象,常常和decltype和sizeof搭配使用。因此,在不创建对象的情况下,可以假设有相应类型的可用对象,即使该类型没有默认构造函数或该类型不可以创建对象。注意,declval只能在unevaluated contexts中使用。
一个简单的例子:
现在如果我想获取使用int调用f()后返回的类型是什么?是
decltype(f(11))
?看起来怪怪的,使用declval看起来就很明了:还有就是之前c++11-17 模板核心知识(一)—— 函数模板中的例子)——返回多个模板参数的公共类型:
这里在为了避免在
?:
中不得不去调用T1 和T2 的构造函数去创建对象,我们使用declval来避免创建对象,而且还可以达到目的。ps. 别忘了使用std::decay_t,因为declval返回的是一个rvalue references. 如果不用的话,max(1,2)
会返回int&&
.最后看下官网的例子:
完美转发 Perfect Forwarding
或者转发临时变量,避免无关的拷贝开销:
作为模板参数的引用
这点也不太常见,在前面的文章c++11-17 模板核心知识(七)—— 模板参数 按值传递 vs 按引用传递提到过一次。这个会改变强制改变模板的行为,即使模板的设计者一开始不想这么设计。
我没怎么见过这种用法,而且这种用法有的时候会有坑,大家了解一下就行。
可以使用static_assert禁止这种用法:
延迟计算 Defer Evaluations
首先引入一个概念:incomplete types. 类型可以是complete或者incomplete,incomplete types包含:
可以理解incomplete types为只是定义了一个标识符但是没有定义大小。例如:
现在回到Defer Evaluations的主题上。考虑如下类模板:
现在这个类可以使用incomplete type,这在某些场景下很重要,例如链表节点的简单实现:
但是,一旦使用一些type_traits,类就不再接受incomplete type:
std::conditional
也是一个type_traits,这里的意思是:根据T是否支持移动语义,来决定foo()返回T &&
还是T &
.但是问题在于,
std::is_move_constructible
需要它的参数是一个complete type. 所以,之前的struct Node这种声明会失败(不是所有的编译器都会失败。其实这里我理解不应该报错,因为按照类模板实例化的规则,成员函数只有用到的时候才进行实例化)。我们可以使用Defer Evaluations来解决这个问题:
这样,编译器就会直到foo()被complete type的Node调用时才实例化。
(完)
朋友们可以关注下我的公众号,获得最及时的更新:
The text was updated successfully, but these errors were encountered: