C++17版本中,如果将bar::baz用作内联,则该代码可以完美编译。而使用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)
{
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
非 const 引用不允许我们转发参数,如果它是左值,例如:
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);
int n2 = f(0);
int n3 = g(i);
A forwarding reference is an rvalue reference to a cv-unqualified
template parameter. If P is a forwarding reference and the argument is
an lvalue, the type “lvalue reference to A” is used in place of A for
type deduction.