考虑到 bar::baz 作为内联的使用,C++17 的代码编译完美,C++14 的模板需要 prvalue 作为参数,因此编译器bar::baz
在目标代码中保留一个符号。这不会得到解决,因为你没有那个声明。constexpr
在可能导致不同方法的代码生成中,编译器应将其视为 constprvalue 或 rvalues。例如,如果被调用函数是内联的,编译器可能会生成使用该特定值作为处理器指令的常量参数的代码。这里的关键词是“应该”和“可能”,它们与一般标准文档状态中通常的免责声明条款中的“必须”不同。
对于原始类型,对于时间值,constexpr
您使用的模板签名没有区别。编译器实际上如何实现它,取决于平台和编译器......以及使用的调用约定。我们甚至无法确定某些东西是否在堆栈上,因为某些平台没有堆栈,或者它的实现方式与 x86 平台上的堆栈不同。多个现代调用约定确实使用 CPU 的寄存器来传递参数。
如果您的编译器足够现代,您根本不需要引用,复制省略将使您免于额外的复制操作。为了证明:
#include <iostream>
template<typename T>
void foo(T x) { std::cout << x.baz << std::endl; }
#include <iostream>
using namespace std;
struct bar
{
int baz;
bar(const int b = 0): baz(b)
{
cout << "Constructor called" << endl;
}
bar(const bar &b): baz(b.baz) //copy constructor
{
cout << "Copy constructor called" << endl;
}
};
int main()
{
foo(bar(42));
}
将导致输出:
Constructor called
42
通过引用传递,通过 const 引用不会比按值传递花费更多,尤其是对于模板。如果您需要不同的语义,则需要显式专门化模板。一些较旧的编译器无法以适当的方式支持后者。
template<typename T>
void foo(const T& x) { std::cout << x.baz << std::endl; }
// ...
bar b(42);
foo(b);
输出:
Constructor called
42
非常量引用不允许我们转发参数,如果它是一个左值,例如
template<typename T>
void foo(T& x) { std::cout << x.baz << std::endl; }
// ...
foo(bar(42));
通过调用此模板(称为完美转发)
template<typename T>
void foo(T&& x) { std::cout << x << std::endl; }
可以避免转发问题,尽管此过程也将涉及复制省略。编译器从 C++17 推导出模板参数如下
template <class T> int f(T&& heisenreference);
template <class T> int g(const T&&);
int i;
int n1 = f(i); // calls f<int&>(int&)
int n2 = f(0); // calls f<int>(int&&)
int n3 = g(i); // error: would call g<int>(const int&&), which
// would bind an rvalue reference to an lvalue
转发引用是对 cv 非限定模板参数的右值引用。如果 P 是转发引用并且参数是左值,则使用类型“对 A 的左值引用”代替 A 进行类型推导。