从 C++ 模板中的非 const 左值引用推导出 const 左值引用
Deducing a const l-value reference from a non-const l-value reference in C++ template
假设您有以下一对函数:
void f(const int&) {
// Do something, making a copy of the argument.
}
void f(int&&) {
// Do the same thing, but moving the argument.
}
它们相当多余——函数之间的唯一区别在于它们是复制还是移动参数。当然,我们可以通过将其重写为单个模板函数来做得更好:
template<typename T>
void g(T&&) {
// Do something, possibly using std::forward to copy or move the argument.
}
这个行得通,是实践中常用的成语。但是模板可能会被实例化为三个函数,而不是我们上面的两个。我们可以使用以下代码验证这种情况:
#include <iostream>
template<typename T> constexpr char *type = nullptr;
template<> constexpr const char *type<int&> = "int&";
template<> constexpr const char *type<const int&> = "const int&";
template<> constexpr const char *type<int> = "int";
template<typename T>
void g(T&&) {
std::cout << reinterpret_cast<void*>(&g<T>)
<< " = &g<" << type<T> << ">" << std::endl;
}
int main() {
int i = 0;
const int& cr = 0;
g(i);
g(cr);
g(0);
return 0;
}
/*
Prints:
0x100f45080 = &g<int&>
0x100f45100 = &g<const int&>
0x100f45180 = &g<int>
*/
这为 T = int&
的情况添加了第三个函数,当我们使用上面的非模板函数 f
时我们没有。在这种情况下,我们实际上不需要函数的这个非常量左值引用版本——给定 f
足以满足我们最初的需求——这会增加代码的大小,尤其是当我们有很多以这种方式编写的相互调用的模板函数。
有没有办法把我们上面的函数g
写成这样,在我们的示例代码中调用g(i)
时,编译器会自动推导出T = const int&
?即,一种我们不必手动编写 g<const int&>(i)
但仍能获得所需行为的方法。
说 "forward references" ("universal references") 比专用重载更好是一种主观的观点。在很多情况下确实如此,但如果您想完全控制它们,它们将无法完成所有工作。
您可以明确确保用户不会传递非常量左值引用,方法是添加
static_assert(!std::is_lvalue_reference<T>::value || std::is_const<typename std::remove_reference<T>::type>::value, "only call g with const argument");
inside g,但这并不是在所有情况下都是很好的解决方案。
或者您执行 vector::push_back(...) 的操作并提供显式重载——但这是您的起点,请参阅 https://en.cppreference.com/w/cpp/container/vector/push_back。
'correct' 答案仅取决于您的要求。
编辑:
@Sjoerd 的建议类似于:
template <typename T>
class aBitComplicated {
public:
void func(T&& v) { internal_func(std::forward<T>(v)); }
void func(const T& v) { internal_func(v); }
private:
template <typename U>
void internal_func(U&& v) { /* your universal code*/ }
};
还有更多的 sophisticated/complicated 版本,但这里应该是实现您要求的最简单的版本。
假设您有以下一对函数:
void f(const int&) {
// Do something, making a copy of the argument.
}
void f(int&&) {
// Do the same thing, but moving the argument.
}
它们相当多余——函数之间的唯一区别在于它们是复制还是移动参数。当然,我们可以通过将其重写为单个模板函数来做得更好:
template<typename T>
void g(T&&) {
// Do something, possibly using std::forward to copy or move the argument.
}
这个行得通,是实践中常用的成语。但是模板可能会被实例化为三个函数,而不是我们上面的两个。我们可以使用以下代码验证这种情况:
#include <iostream>
template<typename T> constexpr char *type = nullptr;
template<> constexpr const char *type<int&> = "int&";
template<> constexpr const char *type<const int&> = "const int&";
template<> constexpr const char *type<int> = "int";
template<typename T>
void g(T&&) {
std::cout << reinterpret_cast<void*>(&g<T>)
<< " = &g<" << type<T> << ">" << std::endl;
}
int main() {
int i = 0;
const int& cr = 0;
g(i);
g(cr);
g(0);
return 0;
}
/*
Prints:
0x100f45080 = &g<int&>
0x100f45100 = &g<const int&>
0x100f45180 = &g<int>
*/
这为 T = int&
的情况添加了第三个函数,当我们使用上面的非模板函数 f
时我们没有。在这种情况下,我们实际上不需要函数的这个非常量左值引用版本——给定 f
足以满足我们最初的需求——这会增加代码的大小,尤其是当我们有很多以这种方式编写的相互调用的模板函数。
有没有办法把我们上面的函数g
写成这样,在我们的示例代码中调用g(i)
时,编译器会自动推导出T = const int&
?即,一种我们不必手动编写 g<const int&>(i)
但仍能获得所需行为的方法。
说 "forward references" ("universal references") 比专用重载更好是一种主观的观点。在很多情况下确实如此,但如果您想完全控制它们,它们将无法完成所有工作。
您可以明确确保用户不会传递非常量左值引用,方法是添加
static_assert(!std::is_lvalue_reference<T>::value || std::is_const<typename std::remove_reference<T>::type>::value, "only call g with const argument");
inside g,但这并不是在所有情况下都是很好的解决方案。
或者您执行 vector::push_back(...) 的操作并提供显式重载——但这是您的起点,请参阅 https://en.cppreference.com/w/cpp/container/vector/push_back。
'correct' 答案仅取决于您的要求。
编辑: @Sjoerd 的建议类似于:
template <typename T>
class aBitComplicated {
public:
void func(T&& v) { internal_func(std::forward<T>(v)); }
void func(const T& v) { internal_func(v); }
private:
template <typename U>
void internal_func(U&& v) { /* your universal code*/ }
};
还有更多的 sophisticated/complicated 版本,但这里应该是实现您要求的最简单的版本。