C++ View Types: Pass by Const& or by Value

C++ view types: pass by const& or by value?

When in doubt, pass by value.

Now, you should only rarely be in doubt.

Often values are expensive to pass and give little benefit. Sometimes you actually want a reference to a possibly mutating value stored elsewhere. Often, in generic code, you don't know if copying is an expensive operation, so you err on the side of not.

The reason why you should pass by value when in doubt is because values are easier to reason about. A reference (even a const one) to external data could mutate in the middle of an algorithm when you call a function callback or what have you, rendering what seems to be a simple function into a complex mess.

In this case, you already have an implicit reference bind (to the contents of the container you are viewing). Adding another implicit reference bind (to the view object that looks into the container) is no less bad because there are already complications.

Finally, compilers can reason about values better than they can about references to values. If you leave the locally analyzed scope (via a function pointer callback), the compiler has to presume the value stored in the const reference may have completely changed (if it cannot prove the contrary). A value in automatic storage with nobody taking a pointer to it can be assumed to not modify in a similar way -- there is no defined way to access it and change it from an external scope, so such modifications can be presumed to not-happen.

Embrace the simplicity when you have an opportunity to pass a value as a value. It only happens rarely.

Is it better in C++ to pass by value or pass by reference-to-const?

It used to be generally recommended best practice1 to use pass by const ref for all types, except for builtin types (char, int, double, etc.), for iterators and for function objects (lambdas, classes deriving from std::*_function).

This was especially true before the existence of move semantics. The reason is simple: if you passed by value, a copy of the object had to be made and, except for very small objects, this is always more expensive than passing a reference.

With C++11, we have gained move semantics. In a nutshell, move semantics permit that, in some cases, an object can be passed “by value” without copying it. In particular, this is the case when the object that you are passing is an rvalue.

In itself, moving an object is still at least as expensive as passing by reference. However, in many cases a function will internally copy an object anyway — i.e. it will take ownership of the argument.2

In these situations we have the following (simplified) trade-off:

  1. We can pass the object by reference, then copy internally.
  2. We can pass the object by value.

“Pass by value” still causes the object to be copied, unless the object is an rvalue. In the case of an rvalue, the object can be moved instead, so that the second case is suddenly no longer “copy, then move” but “move, then (potentially) move again”.

For large objects that implement proper move constructors (such as vectors, strings …), the second case is then vastly more efficient than the first. Therefore, it is recommended to use pass by value if the function takes ownership of the argument, and if the object type supports efficient moving.


A historical note:

In fact, any modern compiler should be able to figure out when passing by value is expensive, and implicitly convert the call to use a const ref if possible.

In theory. In practice, compilers can’t always change this without breaking the function’s binary interface. In some special cases (when the function is inlined) the copy will actually be elided if the compiler can figure out that the original object won’t be changed through the actions in the function.

But in general the compiler can’t determine this, and the advent of move semantics in C++ has made this optimisation much less relevant.


1 E.g. in Scott Meyers, Effective C++.

2 This is especially often true for object constructors, which may take arguments and store them internally to be part of the constructed object’s state.

Does passing fundamental values by const reference really hurt performance?

On platforms where the fundamental type in question fits into a register, a decent compiler should eliminate const references from parameters if it can see both sides of the call. For templates that is usually a given (unless they were explicitly instantiated somewhere). Since your library presumably has to be templated all the way down, this will apply to your case.

It's possible that your end users will have bad compilers or platforms where e.g. a double does not fit into a register. I don't see why you'd be incentivized to make micro-optimizations for these particular users, but maybe you do.

It's also possible that you want to explicitly instantiate all templates in your library for some set of types and provide implementation-less header files. In that case the user's compiler must obey whatever calling conventions exist on that platform and will probably pass fundamental types by reference.

Ultimately, the answer is "profile the relevant and representative use cases" if you don't have faith in the compiler(s).


Edit (removed macro solution): As suggested by Jarod42, the C++ way would be using an alias template. This also avoids the lack of deduction that the asker was running into with their original approach:

template<class T>
using CONSTREF = const T&; // Or just T for benchmarking.

https://godbolt.org/z/mopZ6B

As cppreference says:

Alias templates are never deduced by template argument deduction when deducing a template template parameter.

When is a const reference better than pass-by-value in C++11?

The general rule of thumb for passing by value is when you would end up making a copy anyway. That is to say that rather than doing this:

void f(const std::vector<int>& x) {
std::vector<int> y(x);
// stuff
}

where you first pass a const-ref and then copy it, you should do this instead:

void f(std::vector<int> x) {
// work with x instead
}

This has been partially true in C++03, and has become more useful with move semantics, as the copy may be replaced by a move in the pass-by-val case when the function is called with an rvalue.

Otherwise, when all you want to do is read the data, passing by const reference is still the preferred, efficient way.

benefits of passing const reference vs values in function in c++ for primitive types

In every ABI I know of, references are passed via something equivalent to pointers. So when the compiler cannot inline the function or otherwise must follow the ABI, it will pass pointers there.

Pointers are often larger than values; but more importantly, pointers do not point at registers, and while the top of the stack is almost always going to be in cache, what it points at may not. In addition, many ABIs have primitives passed via register, which can be faster than via memory.

The next problem is within the function. Whenever the code flow could possible modify an int, data from a const int& parameter must be reloaded! While the reference is to const, the data it refers to can be changed via other paths.

The most common ways this can happen is when you leave the code the complier can see while understanding the function body or modify memory through a global variable, or follow a pointer to touch an int elsewhere.

In comparison, an int argument whose address is not taken cannot be legally modified through other means than directly. This permits the compiler to understand it isn't being mutated.

This isn't just a problem for the complier trying to optimize and getting confused. Take something like:

struct ui{
enum{ defFontSize=9;};
std:optional<int> fontSize;
void reloadFontSize(){
fontSize=getFontSizePref();
fontSizeChanged(*fontSize),
}
void fontSizeChanged(int const& sz){
if(sz==defFontSize)
fontSize=std:nullopt;
else
fontSize=sz;
drawText(sz);
}
void drawText(int sz){
std::cout << "At size " << sz <<"\n";
}
};

and the optional, to whom we are passing a reference, gets destroyed and used after destruction.

A bug like this can be far less obvious than this. If we defaulted to passing by value, it could not happen.

Why pass by value and not by const reference?

There are situations where you don't modify the input, but you still need an internal copy of the input, and then you may as well take the arguments by value. For example, suppose you have a function that returns a sorted copy of a vector:

template <typename V> V sorted_copy_1(V const & v)
{
V v_copy = v;
std::sort(v_copy.begin(), v_copy.end());
return v;
}

This is fine, but if the user has a vector that they never need for any other purpose, then you have to make a mandatory copy here that may be unnecessary. So just take the argument by value:

template <typename V> V sorted_copy_2(V v)
{
std::sort(v.begin(), v.end());
return v;
}

Now the entire process of producing, sorting and returning a vector can be done essentially "in-place".

Less expensive examples are algorithms which consume counters or iterators which need to be modified in the process of the algorithm. Again, taking those by value allows you to use the function parameter directly, rather than requiring a local copy.

Why pass by const reference instead of by value?

There are two main considerations. One is the expense of copying the passed object and the second is the assumptions that the compiler can make when the object is a a local object.

E.g. In the first form, in the body of f it cannot be assumed that a and b don't reference the same object; so the value of a must be re-read after any write to b, just in case. In the second form, a cannot be changed via a write to b, as it is local to the function, so these re-reads are unnecessary.

void f(const Obj& a, Obj& b)
{
// a and b could reference the same object
}

void f(Obj a, Obj& b)
{
// a is local, b cannot be a reference to a
}

E.g.: In the first example, the compiler may be able to assume that the value of a local object doesn't change when an unrelated call is made. Without information about h, the compiler may not know whether an object that that function has a reference to (via a reference parameter) isn't changed by h. For example, that object might be part of a global state which is modified by h.

void g(const Obj& a)
{
// ...
h(); // the value of a might change
// ...
}

void g(Obj a)
{
// ...
h(); // the value of a is unlikely to change
// ...
}

Unfortunately, this example isn't cast iron. It is possible to write a class that, say, adds a pointer to itself to a global state object in its constructor, so that even a local object of class type might be altered by a global function call. Despite this, there are still potentially more opportunities for valid optimizations for local objects as they can't be aliased directly by references passed in, or other pre-existing objects.

Passing a parameter by const reference should be chosen where the semantics of references are actually required, or as a performance improvement only if the cost of potential aliasing would be outweighed by the expense of copying the parameter.

why passing string_view by value is faster than const reference

Indirection through a reference (as well as a pointer) has a cost. That cost can be more than the cost of copying a few bytes. As in most cases, you need to verify through measurement whether that is true for your use case / target system. Note that if the function is expanded inline, then there is unlikely to be any difference as you may end up with identical assembly in either case. Even if not, the difference may be extremely small and hard to measure.

Pass int by const reference or by value , any difference?

For primitive types, passing by value is much better than passing by reference. Not only is there no indirection, but with a reference, the compiler has to worry about potential aliasing, which can ruin optimization opportunities.

Finally, pass-by-reference causes lvalues to become odr-used, which can actually cause linker errors. And this final issue doesn't go away if the call gets inlined.

Pass by value or const reference?

The article you site is not a good reference for software
engineering. (It is also likely out of date, given that it
talks about move semantics and is dated from 2003.)

The general rule is simple: pass class types by const reference,
and other types by value. There are explicit exceptions: in
keeping with the conventions of the standard library, it is also
usual to pass iterators and functional objects by value.

Anything else is optimization, and shouldn't be undertaken until
the profiler says you have to.



Related Topics



Leave a reply



Submit