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

acm picture acm · Dec 2, 2014 · Viewed 9.1k times · Source

This came up in a code review discussion recently, but without a satisfactory conclusion. The types in question are analogues to the C++ string_view TS. They are simple non-owning wrappers around a pointer and a length, decorated with some custom functions:

#include <cstddef>

class foo_view {
public:
    foo_view(const char* data, std::size_t len)
        : _data(data)
        , _len(len) {
    }

    // member functions related to viewing the 'foo' pointed to by '_data'.

private:
    const char* _data;
    std::size_t _len;
};

The question arose as to whether there is an argument either way to prefer to pass such view types (including the upcoming string_view and array_view types) by value or by const reference.

Arguments in favor of pass by value amounted to 'less typing', 'can mutate the local copy if the view has meaningful mutations', and 'probably no less efficient'.

Arguments in favor of pass-by-const-reference amounted to 'more idiomatic to pass objects by const&', and 'probably no less efficient'.

Are there any additional considerations that might swing the argument conclusively one way or the other in terms of whether it is better to pass idiomatic view types by value or by const reference.

For this question it is safe to assume C++11 or C++14 semantics, and sufficiently modern toolchains and target architectures, etc.

Answer

Yakk - Adam Nevraumont picture Yakk - Adam Nevraumont · Dec 2, 2014

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.