使用std::ref和std::cref
从 C++11 开始,可以让调用者自行决定向函数模板传递参数的方式。如果模板参数被声明成 按值传递的,调用者可以使用定义在头文件<functional>中的 std::ref()和std::cref()将参数按引用传递给函数模板,比如:
#include <functional>template <typename T>
void square(T a) { a *= a; }int main(int argc, char **argv)
{double a = 1.414;square(std::ref(a));
}
上面的例子似乎不足以体现std::ref或std::cref的重要性,再来看一例子:
#include <functional>
#include <algorithm>
#include <vector>template <typename T>
void add(T &raw, T &sum) {sum += raw;raw += 1;
}int main(int argc, char **argv) {int sum = 0;std::vector<int> v1 {1, 2, 3, 4, 5};std::for_each(v1.begin(), v1.end(), std::bind(add<int>, std::placeholders::_1, sum));return 0;
}
执行过程中,会发现sum并没有按引用传递,而是按值传递。很明显,这不符合预期。要解决这个问题,调用std::for_each时,使用std::ref(sum)替换sum即可,如下:
...
std::for_each(v1.begin(), v1.end(), std::bind(add<int>, std::placeholders::_1, std::ref(sum)));
...
但使用std::ref时要注意一个问题,其返回的是reference_wrapper,而是不是原始参数类型,因此,下面的代码是无法通过编译的:
//...
template <typename T>
void sub(T a, T b, T r) { r = a - b; }
//...double x = 10.1;double y = 3.5;double z = 0.0;//note: candidate template ignored: deduced conflicting types for parameter 'T' ('double' vs. 'reference_wrapper<double>')sub(x, y, std::ref(z));
//...
处理字符串常量和裸数组
对于字符串常量和裸数组做模板参数:
- 按值传递,参数类型退化成指针
- 按引用传递,参数类型不退化,是指向数组的引用。
因此,对于下面的源码无法编译通过:
//...
template <typename T>
int compare(const T &lhs, const T &rhs) { return strcmp(lhs, rhs); }int main(int argc, char **argv)
{compare("hello", "abc");return 0;
}
//...
"hello"和"abc"分别被推导成char[6],和char[4],编译器找不到合适的函数模板,详细错误如下:
error: no matching function for call to 'compare'compare("hello", "abc");note: candidate template ignored: deduced conflicting types for parameter 'T' ('char[6]' vs. 'char[4]')
int compare(const T &lhs, const T &rhs) { return strcmp(lhs, rhs); }
上面的问题可以通过函数重载解决,重载的方法有多种,但普通函数重载最为简单,也更直接明了。如下:
//通用性太差,无法区分数组变量和普通变量
template <typename T>
int compare(T lhs, T rhs) { return strcmp(lhs, rhs); }//过于繁琐
template <typename T, size_t L1, size_t L2>
int compare(T (&lhs)[L1], T (&rhs)[L2]) { return strcmp(lhs, rhs); }int compare(const char *lhs, const char *rhs) { return strcmp(lhs, rhs); }
总体看来,还是普通函数重载更为简洁。
处理返回值
返回值既可以是值,也可以是引用。以下三种场景,函数通常会返回引用:
- 需要直接修改返回的对象
- 提升效率,重新生成一个对象代价较高,如size比较大的容器
- 为链式调用返回一个对象,如std::string的+操作符,重载<<,>>操作符
但在某些情况下,可能更希望函数按值返回。对于普通函数,要做到这点很容易,但对于函数模板,要做到这点,就比较困难。如下面的例子,函数返回的便是引用:
#include <string>
#include <iostream>template <typename T>
T retval(T val) { return T{val}; }int main(int argc, char **argv)
{std::string str0 = "hello";std::cout << std::is_same<std::string, decltype(retval<std::string &>(str0))>::value << std::endl;std::cout << std::is_same<std::string, std::remove_reference<decltype(retval<std::string &>(str0))>::type>::value << std::endl;return 0;
}
如果想函数按值返回,有三种解决方案:
- 使用std::remove_reference推导返回值类型去掉引用修饰
template <typename T>
typename std::remove_reference<T>::type retval(T val) { return T{val}; }
- 使用std::decay推导返回值类型去掉引用修饰
template <typename T>
typename std::decay<T>::type retval(T val) { return T{val}; }
- 使用auto推导返回值类型去掉引用修饰。不过该方案直到c++14之后,才能实现去掉引用修饰的目的;c++11要实现这一目的,比较复杂,因为c++11使用auto需要后跟类型推导。实现如下:
//c++11实现返回值无法去掉引用
template <typename T>
auto retval(T val) -> decltype(val) { return T{val}; }//c++14实现返回值已去掉引用
template <typename T>
auto retval(T val) { return T{val}; }