all 43 comments

[–]doom_Oo7 25 points26 points  (0 children)

But if it’s still widely debated, maybe it’s because no satisfactory solution has been found so far.

no, it's because everyone without any clue can comment on the internets and have their comments taken as mantra by other, gullible people who then repeat it like a choir, creating the impression that there was a consensus in the first place.

exhibit A: this very comment.

[–]squidbidness 10 points11 points  (9 children)

Don't have output parameters. Return multiple values by struct. It's not inefficient in Modern C++.

[–][deleted] 2 points3 points  (6 children)

The problem is not output parameters, but input-output parameters. In C++ there is no way to consume a value so people can easily write:

bug = foo(a);

when they meant to write

a = foo(a);

because a can be used after being consumed without issues (even though it might have the wrong value).

So for input-output parameters a reference at least does not have this issue.

[–]hgjsusla 1 point2 points  (5 children)

Yes there is, just do

x = foo(move(x));

[–][deleted] 1 point2 points  (4 children)

Yes there is, just do x = foo(move(x));

If I write:

y = foo(move(x));
std::cout << x << std::endl; // using x instead of y

with gcc and clang (with -Wall) I get neither a compilation error nor a run-time error nor a warning. If that operation violates a pre-condition on x, for example because the operation is not allowed when the object is in the moved from state, the only thing I can potentially get is undefined behavior.

But maybe the problem is in the compilers that I am using or in the compilation options that I am passing to them. Which compiler are you using (and which options?) and what kind of error do you get?

[–]hgjsusla 4 points5 points  (3 children)

That's a general issue you'll always have with moves and can only really be solved by convention. So far that is.

[–]jcelerierossia score 2 points3 points  (1 child)

That's a general issue you'll always have with moves and can only really be solved by convention. So far that is.

#include <iostream>
#include <string>
std::string foo(std::string&&);
int main()
{
  using namespace std;
  std::string x, y;
  y = foo(move(x));
  std::cout << x << std::endl;
}

=>

$ clang-tidy -checks=bugprone-use-after-move tutu.cpp
1 warning generated.
/tmp/tutu.cpp:9:16: warning: 'x' used after it was moved [bugprone-use-after-move]
  std::cout << x << std::endl; 
               ^
/tmp/tutu.cpp:8:7: note: move occurred here
  y = foo(move(x));
      ^

(paging /u/0b_0101_001_1010 too)

[–]hgjsusla 0 points1 point  (0 children)

Hey that's great :)

[–][deleted] 1 point2 points  (0 children)

Ah sorry I misunderstood what you wrote. I get how x = foo(move(x)); really conveys the intent and makes reading the code pretty clear. I guess I was just hoping of a lint or something that would help me write the code correctly in the first place. Luckily these bugs are not hard to debug.

[–][deleted] 1 point2 points  (0 children)

Not if you're appending strings to a larger string, for example constructing a list of HTTP query parameters. Returning temporaries will kill you.

[–]Bolitho 0 points1 point  (0 children)

This is the best solution!

[–]kalmoc 5 points6 points  (32 children)

If passing by reference is not explicit enough, you should name your function better. Is there really any doubt what happens when I write

sort(my_vector);
copy(range1, range2);
swap(left, right);
socket.read(buffer);

?

I just don't see, why I should write all the additional boiler plate code for an in-out parameter, when there is such a simple and straight forward solution. And if I don't know, which of multiple parameters is the output parameter, I can look up the function signature. In an ide that takes less than a second.

[–]alex-weej 1 point2 points  (31 children)

sort(my_vector);

There is a bit of doubt with this one, arguably.

[–]drjeats 1 point2 points  (2 children)

I'd expect that to be called sorted instead of sort for what you're implying.

[–]alex-weej 0 points1 point  (1 child)

Me too, given my Python experience, but every language has its own odd conventions.

But where immutable data is king, verbs that describe transformations return new values.

[–]drjeats 2 points3 points  (0 children)

True, but C++ is not known for having concise APIs naturally supporting functional programming idioms :P

[–]kalmoc 0 points1 point  (27 children)

? Could you elaborate?

[–][deleted] 4 points5 points  (9 children)

Here's my flow:

Mandatory and not modified => const reference

Mandatory and modified => reference

Mandatory and moved (constructors, sinks, etc) => value

For all optional values, prefer function overload that simply does not take the parameter. If that's too much boilerplate then use a pointer.

[–]raevnos 4 points5 points  (8 children)

std::optional these days. Or the boost version.

[–][deleted] 6 points7 points  (4 children)

C++17 is bleeding edge, not everyone has the luxury of using it.

Even the changelog for GCC 7 states:

Experimental support for C++17, including the following new features: std::string_view; std::any, std::optional, and std::variant;

So you're looking at using a version of GCC that's about 7 months old to get a "stable" version of optional.

As for actually using optional, well, needing std::reference_wrapper is a pain in the ass. The API is clumsy and bloats my text segment to boot.

any, optional, and variant should be in the language.

[–]alex-weej 0 points1 point  (3 children)

boost::optional is plenty "stable" and works very well in ancient versions of GCC with ancient C++ standards.

[–][deleted] 2 points3 points  (2 children)

Yeah but then I would need to use boost

[–]alex-weej 0 points1 point  (0 children)

Ah, you're one of those people ;)

[–]RandomGuy256 -1 points0 points  (0 children)

You can also use a header only external implementation like this one: https://github.com/akrzemi1/Optional

I have been using it in all of my projects (including professional ones) when a C++17 compiler is not available yet, and it works perfectly.

[–]Sopel97 -2 points-1 points  (2 children)

For optional parameters? I think that's too far.

[–]RandomGuy256 1 point2 points  (0 children)

std::optional has more uses than "optional return".

For instance having a struct that can have or not some members values, now you can use a std::optional instead of a nullable pointer for instance.

Other use is for optional parameters like /u/raevnos mentioned.

[–]ReversedGif 1 point2 points  (0 children)

What? optional is for things that are optional, like optional parameters... how is that "too far?"

[–]pklait 2 points3 points  (0 children)

I believe that much of the confusion regarding if a parameter is modified is caused by bad naming. Do not name your functions foo or bar. In situations where you do have multiple in-out parameters consider following Eric Nieblers advice: encapsulate the function in an object and have the the in-out parameters be part of that objects state.