6

Recently, during code review, I stumbled upon the std::string_view argument passed by reference. So for example the following code:

void fun(std::string_view a);

becomes

void fun(std::string_view& a);

I found it troubling for the following reasons:

  • It can no longer be invoked with anything other than l-value std::string_view.
  • The whole point of using std::string_view is about avoiding copying the underlying string, so I see no point of using & with it.

The question is, Is there any advantage of declaring std::string_view argument as passed by reference instead of passed by value?

Łukasz Kosiak
  • 513
  • 6
  • 22
  • 4
    You probably mean other than lvalue string_view? – StoryTeller - Unslander Monica Oct 16 '19 at 10:09
  • 1
    Also: related, possible duplcate https://stackoverflow.com/questions/27256377/c-view-types-pass-by-const-or-by-value – StoryTeller - Unslander Monica Oct 16 '19 at 10:10
  • Do you mean a pass by a mutable reference as currently written, or `const&`? Without `const` there's a bold semantic difference, the original object is mutable within callee, so I'd expect `fun` to change state of operand. `const&` type is much closer to pass by value in terms of invariance but still not exactly the same. If your promises about state are the same as pass by value i.e. no possible change at all and you consider using reference just for performance, I agree with @StoryTeller the answer in the other question cover your topic. – luk32 Oct 16 '19 at 11:50

1 Answers1

7

The question is a bit unclear, since the two are semantical very different.

Passing a non-const reference means return by parameter - ie. you modify the value inside the function at some point and the original input value may be used or not. Since you can only point the string_view to something else and are not allowed to modify the contents. So I should expect the following - the function uses the input string_view for something and then later points this input strint_view to something else. A semantical improvement would be:

  void fun(std::string_view& a);

improved version:

 std::string_view fun(const std::string_view& a);

even better solution (semanticly):

 std::string_view fun(std::string_view a);

Now It is easier to provide the input and I get to choose myself if I want my input string_view repoint'ed.


Other than that the reference is at a disadvantage here due to the following:

  • Semantical a copy is better: it is easier to reason about a copy than a reference (even a const one).
  • If performance is a consideration as well, the reference stands at a disadvantage in most scenarios and at best it looks like it will only be roughly on par with the copy. Demonstation using clang: https://godbolt.org/z/UR68km (results can vary due to compiler)

Please note even if the copy turned out to be slower by a small margin - it should still be the preferred method of passing.


I usually find myself using the following mnemonic when writing functions:

Pass by const reference, return by value. Except when you can, pass by value instead.

It's fairly fitting here. The when you can includes when theres no potential large data copy involved and this is case here (there may be situations when reference is the only option). Using this rule of thumb ensures you don't make extra large data copies by accident. Apart from scalars, the most notable class types that should be passed by value is those that fall within the proxy category (for example smart pointers and views).

Only rarely should you pass by non-const reference - the usual case is when a buffer is modified. The same goes for pointers to some extent.

darune
  • 10,480
  • 2
  • 24
  • 62
  • 1
    The one place i've found use with a pass by reference is when extracting "tokens". I.e. a declaration of std::string_view ExtractNextToken(std::string_view& buffer). In this way, the param is essentially treated like a queue with tokens being popped off from the buffer. Empty string view signifying theres no more tokens to be extracted (can also check buffer.size() == 0). All other cases, pass by value/copy seems to be the way to go – bgura Feb 09 '23 at 04:06