使用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}; }