News for hackers. Stuff that matters.
Re:Const all the things? by Karellen on Monday January 24, 2022 @06:00AM (#30056007)
Or, don't use out-parameters unless you really have to.

If a function returns a value, have it return that value, instead of using an out-parameter. Passing in-parameters by const reference rather than by value is a nice win because the semantics of the function remain clear. Input parameters still look like input parameters, you're just preventing unnecessary copies where it's easy to do so. Returning a value as an out-parameter rather than just returning it is a performance hack that prevents a copy, but at the expense of muddying the semantics of the function.

As the prophet says: we should forget about small efficiencies, say about 97% of the time. Write code that clearly expresses what it does. Return your return values. If you run a profiler over your code and you notice that some returns are causing a performance problem - sure, at that point, add an overload that takes an out-parameter and use it at the frequenty-executed hot spot.

Re:Const all the things? by Denvercoder9 on Monday January 24, 2022 @07:55AM (#30057025)
The most common usage of out-parameters is when you want to return more than one thing from a function, in which case they can't be trivially converted to return values.
Re:Const all the things? by ogogmad on Monday January 24, 2022 @09:01AM (#30057613)
Surely in C++ you can return a tuple. It's only in C you can't.
Re:Const all the things? by Denvercoder9 on Monday January 24, 2022 @09:31AM (#30058010)
Sure, but that doesn't always help readability/clarity. Compare this:

    SomeType result;
    if (!do_something(&result))
      return;
    process(result);
with this:

     std::tuple<bool, SomeType> ret = do_something();
     if (!std::get<0>(ret))
       return;
     process(std::get<1>(ret));
You could improve it a bit by using `auto` or returning a struct with named members, but quite often I still find it less readable. Especially in situations that are more complex than this example, where e.g. you fallback to another function to fetch `result` if `do_something` failed.
Re:Const all the things? by kaashif on Monday January 24, 2022 @09:49AM (#30058244)
If there are only two status codes (success/failure) then it seems like optional would be more appropriate than a tuple of bool and SomeType.

    const std::optional<SomeType> result = do_something();
    if (!result.has_value()) {
        return;
    }
    process(result.value());
This is nicer than the output parameter in my opinion, because it may be unclear what the meaning of a default constructed SomeType is. Default values are sometimes dangerous if the default is a real, valid input - errors and invalid states can end up being passed through.

C++ doesn't have great support for sum types, so this is all always going to be pretty non-ergonomic, and the compiler doesn't always warn if you forget to do things. I might even say that the main advantage of using sum types is gone in C++ - you actually can forget to check has_value and call operator* on an empty optional.

It would be so easy for someone to make a mistake and write:

    process(*do_something());
And invoke undefined behaviour. Whereas processing a default-constructed SomeType is at least not undefined behaviour (depending on what SomeType is, and what process does...).

This whole thing is a mess, I hate C++, thinking about UB all the time...

Re:Const all the things? by PaulDavisThe1st on Monday January 24, 2022 @11:20AM (#30059651)
for performance-oriented code, the original example, where the argument is stack-allocated in the caller, passed by reference, and used based on the return value, has benefits that can't be replicated in the std::optional<> version.

I like the std::optional<> version and use that pattern some of the time. But if the object being passed/returned to/from do_something() is non-trivial, i don't want the copy overhead.

Re:Const all the things? by kaashif on Monday January 24, 2022 @01:06PM (#30061413)
> for performance-oriented code, the original example, where the argument is stack-allocated in the caller, passed by reference, and used based on the return value, has benefits that can't be replicated in the std::optional<> version.

Can you go into detail? What are the benefits that can't be replicated?

If anything, the std::optional version seems better, since we can avoid an invocation of the SomeType constructor. Otherwise, everything is the same - everything's allocated on the stack, there is no dynamic allocation.

> If an optional<T> contains a value, the value is guaranteed to be allocated as part of the optional object footprint, i.e. no dynamic memory allocation ever takes place. Thus, an optional object models an object, not a pointer, even though operator*() and operator->() are defined.

That's from here: https://en.cppreference.com/w/cpp/utility/optional

Since C++17, RVO is required by the standard too:

> Return value optimization is mandatory and no longer considered as copy elision; see above.

From here: https://en.cppreference.com/w/cpp/language/copy_elision

I admit I'm not really a C++ expert, so maybe I'm missing something.

Re:Const all the things? by PaulDavisThe1st on Monday January 24, 2022 @01:45PM (#30062020)
Yes, in C++17, RVO would fix the issue. Alas, some of us are stuck with project conventions that have not reached "use C++17". Without RVO, there's an extra copy.
Re:Const all the things? by masklinn on Monday January 24, 2022 @03:05PM (#30063219)
RVO is not new in C++17 though, only the standard requiring it in some cases.

Unless your compiler is truly ancient odds are good it RVOs just fine no?

Re:Const all the things? by Kranar on Monday January 24, 2022 @11:56AM (#30060304)
There is no copy involved with the std::optional version.
Re:Const all the things? by cyber_kinetist on Monday January 24, 2022 @12:38PM (#30060984)
The thing is, are we sure?

Probably many C++ aficionados will run to godbolt.org to prove this in a simple testcase, and they will probably be right. But then maybe it’s because you’ve only tested this for primitive or POD types. Maybe this might not be guaranteed for non-POD types with custom constructors? Or maybe this might be okay because of RVO or something? Hmm, let us read the specs again…

The problem is, C++ is a language that is very unintuitive about how your code is going to get mapped to actual hardware (assembly code). For many systems developers, it isn’t enough that the compiler might optimize this smartly; we instead want predictable compiler behavior. So if you want to make sure, and you’re not confident about the gnarly details of the C++ specification, it will be better for you just use out parameters instead of std::optional, if you are in the situation where you really need to squeeze out performance (which happens a lot for low-level systems programming).

Re:Const all the things? by Kranar on Monday January 24, 2022 @12:51PM (#30061191)
Yes we are sure and it has nothing to do with optimizations, RVO, Godbolt or any of the completely irrelevant details you are talking about.

The implementation of std::optional is not permitted by the standard to allocate memory.

Re:Const all the things? by cyber_kinetist on Monday January 24, 2022 @01:48PM (#30062069)
Note that I’m not talking about if std::optional itself allocates more than it needs to at construction (which I know it doesn’t, as cppreference.com says so), but if the compiler might generate an additional copy constructor call if it is value-returned from a function (which I’m worried about since NRVO isn’t in the spec and you can’t rely on it).

It’s more specific to the example code the original commenter posted (and about general out-parameter usage) than the flaws of std::optional itself. Maybe this didn’t get communicated properly from my comment.

Re:Const all the things? by PaulDavisThe1st on Monday January 24, 2022 @01:48PM (#30062062)
For C++17, true. Not so if you're using an earlier compiler and/or language standard. Yes, yes, I know we should all get with the program, but that's alas not how the world operates.
Re:Const all the things? by Kranar on Monday January 24, 2022 @01:57PM (#30062210)
There is no std::optional in earlier language standards. std::optional is a C++17 library feature.
Re:Const all the things? by tsimionescu on Monday January 24, 2022 @02:46PM (#30062922)
Even for C++ 17, this is only guaranteed if you don't name the variable in your function. NRVO is not guaranteed at all. There is even an example downthread where gcc can't do it even at -O3.
Re:Const all the things? by PaulDavisThe1st on Monday January 24, 2022 @02:04PM (#30062298)
boost::optional has existed for many, many years and does not require compiler/language support. AFAIK, the behavior is substantially identical to std::optional.
Re:Const all the things? by Kranar on Monday January 24, 2022 @10:38AM (#30058940)
There are numerous ways of handling this that are significantly better, for example using an std::optional yields this code:

    do_something().and_then(process);
Or structured bindings:

    auto [error_code, result] = do_something();
    if(error_code == 0) {
      process(result);
    }
Re:Const all the things? by brandmeyer on Monday January 24, 2022 @11:35AM (#30059957)
In the presence of types which are truthy (ie, contextually convertible to bool), its too easy to accidentally swap the unpacking:

    // Oops, backwards!
    auto [result, error_code] = do_something();
    if(error_code) {
      process(result);
    }
Re:Const all the things? by BoorishBears on Monday January 24, 2022 @01:00PM (#30061317)
To me that's not easy...

It's a very well established pattern to have (error, result) I'd immediately see something off with (result, error)

And clang-tidy can catch this: https://clang.llvm.org/extra/clang-tidy/checks/readability-i...

Re:Const all the things? by Kranar on Monday January 24, 2022 @01:24PM (#30061683)
Fair enough, in my codebase we always order data in terms of dependencies so that independent data comes before dependent data. In this case the result depends on the error_code, and the error_code is independent, so the error_code must be listed first.
Re:Const all the things? by BoorishBears on Monday January 24, 2022 @10:41AM (#30058983)

    auto [error, value] = do_something()
    if(!error)
         process(value)
Is there something wrong with this?
Re:Const all the things? by kaashif on Monday January 24, 2022 @01:12PM (#30061501)
Hmm, is it error, value or value, error? In this situation, I'd prefer something like std::optional or std::variant, which you can't get wrong as easily as swapping two variables in a structured bind.

Those have their problems too, though, and I'd never claim std::variant is "nice" to use.

Re:Const all the things? by jcranberry on Monday January 24, 2022 @05:04PM (#30064695)
tuples are unfortunately terrible. Its almost always better to make another type with the fields you need than use a tuple.
Re:Const all the things? by huhtenberg on Monday January 24, 2022 @09:17AM (#30057804)
You can return a struct in C, but that's as unconventional as it gets.
Re:Const all the things? by thinkharderdev on Monday January 24, 2022 @06:31AM (#30056260)
I still don't really grok move semantics in C++ but is there a difference between assigning output to an out parameter and returning a value using std::move? Conceptually they seem the same to me but from what little I understand std::move doesn't quite do what I think it does.
Re:Const all the things? by jcelerier on Monday January 24, 2022 @06:35AM (#30056295)
You must not use std::move when returning a value, just let RVO do its job. The compiler will make it so that the variable you are returning from your function will be constructed in-place at the variable you are assigning the function call to.

However, if you are compiling at -O0 without any optimizations this may not happen.

Re:Const all the things? by kaashif on Monday January 24, 2022 @09:54AM (#30058303)
Since C++17, compilers are now required to elide copies and use RVO: https://en.cppreference.com/w/cpp/language/copy_elision

There are probably exceptions and loopholes just like with literally everything in C++, but there's this line:

> Return value optimization is mandatory and no longer considered as copy elision; see above.

Re:Const all the things? by jcelerier on Monday January 24, 2022 @10:31AM (#30058844)
That's only in the unnamed case. Here's a simple case than GCC does not optimize even in -std=c++20 (and even at -O3 LOL): https://gcc.godbolt.org/z/jKPr98Mhx
Re:Const all the things? by kaashif on Monday January 24, 2022 @01:26PM (#30061722)
Isn't it kind of confusing that RVO is mandatory, but it turns out RVO only refers to unnamed RVO and not NRVO? I would've thought that since NRVO is a type of RVO...

Thanks for the clarification. Clang does do NRVO in that case but I guess it's obviously not guaranteed by the standard.

Re:Const all the things? by jcelerier on Monday January 24, 2022 @01:52PM (#30062131)
Yes, I believe that explicitly qualifying unnamed and named rvo should be the norm and would clarify a lot of confusion on these topics.

I guess it makes sense that an optimization that GCC is not able to do at all should not be part of the standard ; I'm pretty sure it should be possible to write cases that clang wouldn't be able to optimize either.

Re:Const all the things? by cyber_kinetist on Monday January 24, 2022 @12:41PM (#30061045)
Oof, that’s a bit painful. I thought you could rely on NRVO for recent versions of gcc and clang, but feel I should be a lot more careful after seeing that example.
Re:Const all the things? by jcelerier on Tuesday January 25, 2022 @05:09PM (#30078818)
From what I could see through hundreds of godbolt snippets, if you declare the variable you'll return at the very top of the function before any control flow, NRVO will pretty much always happen.
Re:Const all the things? by jcranberry on Monday January 24, 2022 @05:13PM (#30064804)
That only applies when you're returning a prvalue. NRVO isn't guaranteed: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=58055
Re:Const all the things? by bergesenha on Monday January 24, 2022 @09:55AM (#30058315)
Since C++17 I believe this is guaranteed.
Re:Const all the things? by erwincoumans on Monday January 24, 2022 @09:43AM (#30058142)
Just like you want to avoid a copy of the input arguments, you may also want to avoid a copy of the output/return value.