With C++17 that code compiles perfectly considering usage of bar::baz as inline, with C++14 the template requires prvalue as an argument, so compiler retains a symbol for bar::baz
in object code. Which will not get resolved because you didn't had that declaration. constexpr
should be treated as constprvalue or rvalues by compiler, in code generation that may lead to different approach. E.g. if called function is inline, compiler may generate code that is using that particular value as constant argument of processor's instruction. Keywords here are "should be" and "may", which are as different from "must" as usual disclaimer clause in general standard documentation states.
For a primitive type, for a temporal value and constexpr
there will be no difference, in which template signature you do use. How actually compiler implements it, depends on platform and compiler... and calling conventions used. we can't really even tell if something is on stack for sure, because some platform do NOT have stack or it is implemented differently from stack on x86 platform. Multiple modern calling conventions do use registers of CPU to pass arguments.
If your compiler is modern enough you don't need references at all, copy elision would save you from extra copy operations. To prove that:
#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));
}
will result in output:
Constructor called
42
Passing by reference, by a const reference wouldn't cost more than passing by value, especially for templates. If you need different semantics, you would require explicit specialization of template. Some older compilers couldn't support the latter in proper way.
template<typename T>
void foo(const T& x) { std::cout << x.baz << std::endl; }
// ...
bar b(42);
foo(b);
Output:
Constructor called
42
Non-const reference would not allow us to forward argument, if it was an lvalue, e.g
template<typename T>
void foo(T& x) { std::cout << x.baz << std::endl; }
// ...
foo(bar(42));
by calling this template (called perfect forwarding )
template<typename T>
void foo(T&& x) { std::cout << x << std::endl; }
one would be able to avoid forwarding problems, though this process would
also involve copy elision. Compiler deduces template parameter as follows from 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
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.
movl
that 42 into register just within the code. – Messenger