
Returning multiple values from functions in C++ - ingve
http://eli.thegreenplace.net/2016/returning-multiple-values-from-functions-in-c/
======
jimjimjim
I know it's mentioned specially in the article but the C programmer in me
immediately wants to use structs. They don't need to be complex or long lived
or even initialized. And as a bonus there's no heap involved anywhere. Either
that or just bail out of c++ and use go.

~~~
wall_words
std::tuple is essentially an "anonymous" struct -- there's no allocation
involved

~~~
kzhahou
But the members inside don't have proper names.

~~~
masklinn
That's rarely a problem where MRV makes sense. Though you could always fix it
by using anonymous structs whose fields are both named and positional (similar
to Python's namedtuples)

------
matt42
The problem with deconstructing a tuple is that you can easily make mistake
about the order of its members. The struct is a much better solution to me,
and you can go over the problem of the weirdly named one-off struct by using
the iod library
([https://github.com/matt-42/iod](https://github.com/matt-42/iod)):

    
    
      foo() { return D(_id = "test", _age = "42" }; }
    
      auto r = foo();
      std::cout << r.id << r.age << std::endl;

~~~
ericfrederich
Meaningful names would be nice.

In C++ iterating over a map items you get a pair with ->first() and ->second()
methods instead of key / value.

Python has named tuples which can work both ways.

------
junke
Ada has in, out and in/out parameters which adds a little more semantic to C
and C++'s pointer parameters.

Author mentions Common Lisp's multiple values, and one of the main feature of
multiple values is that they are optional: the caller does not need to use
secondary values. Even though floor returns 2 values, the following is a valid
expression:

    
    
       (+ (floor x) 2)
    

Only the primary value is used. A compiler typically generates code that use
registers: the program does not allocate memory to wrap return values in list
or a structure (all values are still computed, but secondary values are often
additional data that a function needs to compute to determine its primary
result).

~~~
adwf
Sometimes Lisp can get annoying though. I've used libraries where a function
might return 5-6 values and I only need the first and last. I need to bind all
of them and then (declare (ignore...)) the useless bits otherwise I get
compiler warnings.

It's an annoyance, because I always feel the general ethos of Lisp is to
eliminate tedious typing and multiple value returns frequently add a lot of
cruft.

~~~
lispm
Common Lisp is a programmable programming language.

You need to hack around it. Don't like it? Improve it. Sketch:

    
    
        (defun make-vars (vars &aux syms)
          "creates uninterned symbols for vars named NIL.
        Returns a list with those replaced and a list of the new syms."
          (values (loop for var in vars
                        if (eq var NIL)
                        collect (let ((sym (gensym "ignore"))) (push sym syms) sym)
                        else collect var)
                  syms))
    
        (defmacro multiple-value-bind-some (vars form &body body)
          "Similar to MULTIPLE-VALUE-BIND, but variables named NIL will be ignored."
          (multiple-value-bind (vars syms) (make-vars vars)
            `(multiple-value-bind ,vars ,form
               (declare (ignore ,@syms))
               ,@body)))
    

Example

    
    
        (defun foo (x)
          "returns five values"
          (values x (* x 2) (* x 3) (* x 4) (* x 5)))
    
    
        (defun test ()
          (multiple-value-bind-some (a nil nil nil b)
              (foo 10)
            (list a b)))

~~~
junke
I have a hunch that you defined make-vars as a separate function just to be
able to nest two multiple-value-bind, one in the macro and one in the expanded
form ;-)

~~~
lispm
A replacement for MULTIPLE-VALUE-BIND, which uses MULTIPLE-VALUE-BIND itself,
shows a bit of the value of MULTIPLE-VALUE-BIND.

------
JoshTriplett
The getline case (boolean and possible value) really wants a type like
Haskell's "Maybe" or Rust's Option: a type that either contains nothing or a
value.

~~~
jepler
Unfortunately the currently available solution (boost::optional) performs
enough worse that the difference can be measured in a simple implementation of
"wc -l". Consider the following code:

    
    
        #include <iostream>
        #include <string>
        #include <boost/optional.hpp>
    
        using namespace std;
        using namespace boost;
    
        optional<string> getline_(istream &st) {
            std::string line;
            if(getline(st, line)) return line;
            return none;
        }
    
        int main() {
            int lines = 0;
            while(getline_(cin)) lines++;
            cout << lines << "\n";
        }
    

vs

    
    
        using namespace std;
        int main() {
            int lines = 0;
            string line;
            while(getline(cin, line)) lines++;
            cout << lines << "\n";
        }
    

Run on an input file of 172,544 lines (the GPL-3 license repeated 256 times),
the version using "optional" takes 400ms and performs 1,077,504(!) heap
allocations, while the more standard version takes just 280ms and performs
just 8 allocations.

Of course, "wc -l" (GNU) takes just 9ms, so clearly this isn't a great program
either way--but it does serve to show that boost::optional can be a surprising
performance sink compared to using a bool return value + out parameter.

(tests on debian jessie amd64, i5-3320M @ 2.6GHz, g++ 4.9 -O3, boost 1.55.0.2)

~~~
imron
That's not the fault of optional though. It's because with the optional
version you're creating (and destroying) a new string each iteration of the
loop.

Something like this (untested) should be much closer to the non-optional
version:

    
    
        #include <iostream>
        #include <string>
        #include <boost/optional.hpp>
    
        using namespace std;
        using namespace boost;
    
        optional<string&> getline_(istream &st, string& line) {
            if(getline(st, line)) return line;
            return none;
        }   
    
        int main() {
            int lines = 0;
            string line;
            line.reserve( 1024 );  //1024 should be long enough for any line
            while(getline_(cin, line )) lines++;
            cout << lines << "\n";
        }

~~~
yoklov
Which more or less removes any benefit for using optional, unfortunately.

~~~
imron
Yes for this case, but that's just because it's a bad example for comparing
the performance of optional, because the code is doing things that have very
different performance characteristics.

There are plenty of other cases though where it is useful to use optional
(e.g. instead of passing/returning a naked pointer which may or may not be
null) and in those cases use of optional will have little/no overhead.

------
qwertyuiop924
For a moment, I thought they had added true multiple value return to C++. I am
sooo glad they didn't. The lisp folks added it way back, and it has been an
ugly scar on the language ever since. Why do real multi-value returns suck?
because they don't compose well, they're awkward as all heck to use, and
require wrapping certain function calls in really weird constructs. The PROPER
way to do multiple values is to return a data structure and pattern match
against it. Of course, lisp has support for this, multi-value return was just
an optimization hack. WHICH YOU SHOULD NO LONGER USE!

~~~
junke
Multiple values are not "just" an optimization hack, they are designed to be
convenient to use. I don't know which construct you find "weird". Multiple-
value-bind is a descriptive name which is long to type but this does not
matter (type m-v-b + auto-complete).

Typical example: you read a line in a file. The line you get is terminated
either by a newline character or by the end-of file. The implementation of
read-line[1] must know if you reached end of file or not, but most of the
time, you don't care about it. Sometimes, you want to know, and then you can
take the secondary value (missing-newline-p). I like the fact that I can focus
on the primary value without caring about other ones when I want.

[1]
[http://clhs.lisp.se/Body/f_rd_lin.htm](http://clhs.lisp.se/Body/f_rd_lin.htm)

~~~
qwertyuiop924
Maybe this is different in CL, but in scheme, if you're using a procedure that
returns multiple values, there is no default return, or wrapping the multiple
values into a list: You have to use call-with-values, any number of srfis
providing sugar, most commonly srfi-8, although 11 and 71 also exist, and
without the sugar, god knows how many lambdas. EVERY. SINGLE. TIME. You want
to call a function that returns multiple values.

~~~
lispm
You might want to learn Common Lisp BEFORE you make statements about how a
feature of the language is obsolete or not.

~~~
qwertyuiop924
I was talking about the general idea of MVR, AND how I thought is was
implemented in lisp. I was wrong. This happens sometimes.

And even the new explanation, I STILL don't like MVR. I think that returning a
list or other data structure, or just writing two functions, is much clearer,
and lends itself better to function chaining. Sure, in CL there's a default,
which makes things a little better, but what if you want the other one? I
don't want to write

    
    
        (multiple-value-bind (a b) (foo bar baz) b)
    

all over my codebase, and the scheme equivalent is just as bad, if not worse.

~~~
lispm
Why don't you take a bit of time to read about multiple values in Common Lisp?

    
    
        CL-USER 76 > (nth-value 1 (values 'a 'b 'c 'd))
        B
    

While you are at it, check out the feature of 'Macros' in Lisp, which allows
everyone to write code to shorten this stuff. See my code in this thread for a
macro which then allows you to write:

    
    
        (multiple-value-bind-some (NIL b)
           (foo bar baz)
          b)
    

One then can name ignored variables as NIL in the source code...

~~~
qwertyuiop924
Good point. And very true. Still looks a bit ugly to me, but it's better than
nothing, I suppose. And yes, I am aware of macros. For some reason using them
in this situation didn't occur to me, so I guess I am an idiot after all.

This is actually one of the cases where syntax-rules would allow you to write
something cleaner, I would guess. I like syntax rules, but everybody always
seems to complain about it...

------
m_mueller
Instead of markers, I like to use typedefs instead. Here's an example how this
would look like in a HPC related program. Basically, if you know Fortran, you
know where this is coming from.

    
    
        typedef const double * const __restrict__ IN;
        typedef double * const __restrict__ OUT;
        typedef const int INT_VALUE;
        typedef const double FP_VALUE;
    
        void diffuse_c(
            OUT runtime_total_s,
            OUT runtime_boundary_s,
            OUT thermal_energy_updated,
            IN thermal_energy,
            INT_VALUE nx,
            INT_VALUE ny,
            INT_VALUE nz
        );

~~~
Aeolos
I can't be the only one who finds "INT_VALUE" both superfluous and
unnecessarily ugly?

~~~
m_mueller
You're right, I have been thinking about this naming as well. I think in this
context, "INT_PARAM" would be better. I like to typedef it because this way
it's easier to replace with a hardware specific integer type later on if I
decide to do so. Anyways, the "VALUE" naming is coming from Fortran, which is
by default pass-by-reference, so you use it to get pass-by-value.

Edit: Thinking about it some more, "INT_VALUE" still makes sense - it makes it
clear that this specific symbol should be accessed as a value, not as a
pointer. Doing this reminds me that I'm (probably) imposing more register
pressure by passing by value. In general I'd probably pass a const int * const
pointer here, but in this specific case I needed values because of some CUDA
specific limitation.

~~~
Rexxar
> Doing this reminds me that I'm (probably) imposing more register pressure by
> passing by value.

Not for "int"s, I would be very surprised if sizeof(int*) < sizeof(int) on
your system.

~~~
m_mueller
Good point, that was a bit of a brainfart of mine.

------
rileymat2
I am a bit surprised exceptions were not mentioned in the context of returning
an error value.

~~~
eliben
The idea was to show things that actually make sense as return values /
statuses rather than exceptions. I surely wouldn't throw an exception for
"getline reached EOF"

~~~
rileymat2
Yeah, I am not for overusing exceptions, but languages like python make
creative use of exceptions. For instance the generator syntax raises an
exception when it is done. Interestingly, you can make a generator to go
through the lines in a file, and use that when you are at the EOF.

