Among the many benefits of const qualification is to make an API more understandable, example:
template<typename T> int function1(T const& in);
// clearly, the input won’t change through function1
With the introduction of rvalue references, one can benefit from perfect forwarding but often const qualifiers are removed, example:
template<typename T> int function2(T&& in);
// can explicitly forward the input if it's an rvalue
Apart from documentation, is there a good way to describe that function2 won’t change its input?
They are allowed and even functions ranked based on const , but since you can't move from const object referred by const Foo&& , they aren't useful.
The const qualifier explicitly declares a data object as something that cannot be changed. Its value is set at initialization. You cannot use const data objects in expressions requiring a modifiable lvalue. For example, a const data object cannot appear on the lefthand side of an assignment statement.
An lvalue reference can bind to an lvalue, but not to an rvalue.
Now an lvalue reference is a reference that binds to an lvalue. lvalue references are marked with one ampersand (&). And an rvalue reference is a reference that binds to an rvalue. rvalue references are marked with two ampersands (&&).
template<typename T> int function2(T&& in); // can explicitly forward the input if it's an rvalue
Apart from documentation, is there a good way to describe that function2 won’t change its input?
Yes. Stick with the C++03 solution:
template<typename T> int function1(T const& in);
// clearly, the input won’t change through function1
The benefits of perfect forwarding are that you don't want to assume if something is const
or non-const
, lvalue or rvalue. If you want to enforce that something is not modified (i.e. that it is const
), then explicitly say so by adding const
.
You could do this:
template<typename T> int function1(T const&& in);
// clearly, the input won’t change through function1
However everyone who read your code would wonder why you've used rvalue references. And function1
would cease to accept lvalues. Just use const &
instead and everyone will understand. It is a simple and well understood idiom.
You don't want to perfectly forward. You want to enforce immutability.
You could say this:
template <typename T>
typename std::enable_if<immutable<T>::value, int>::type
function(T && in)
{
// ...
}
where you have something like:
template <typename T> struct immutable
: std::integral_constant<bool, !std::is_reference<T>::value> {};
template <typename U> struct immutable<U const &>
: std::true_type {};
This way, the template will only be usable if the universal reference is either a const-reference (so T = U const &
) or an rvalue-reference (so T
is not a reference).
That said, if the argument is not going to be changed, you could just use T const &
and be done with it, since there's nothing to be gained from binding mutably to temporary values.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With