Returning multiple values from functions in C++
eli.thegreenplace.net
eli.thegreenplace.net
> One time structs are usually redeclared on multiple places. It is really hard maintain such code.
Well, if you don't need forward declarations, you can define structs inline in function signature: #include <stdio.h>
struct ret { int a; int b} hello(int c) {
return (struct ret){ c, c + 1 };
}
int main(int argc, char **argv) {
struct ret result = hello(2);
printf("%d %d", result.a, result.b);
}
I'm not sure how portable it is though, works in gcc and in whatever ideone is using: https://ideone.com/bgZUPSAn alternative in C++14:
auto hello(int c) { struct { int a; int b; } result { c, c + 1 }; return result; }
or with gcc extensions: auto hello(int c) { return (struct { int a; int b; }) { c, c + 1 }; }
struct x {int a, char* b}
x foo();
// a lot of code
struct y {char* y, int x}
y bar();
//another header
struct z {bool a, int b, char* c}
z foo2();
It is ussually the same data structure, but someone was lazy to define it properly.So either use structs or totally change language? That doesn't really make sense.
... foo()
{
return { 1, "bar", false };
}
auto { i, s, b } = foo();
It's a syntax sugar, granted, and they could push it further by adopting tuples as a native language construct with some shorthand notation (e.g. [...]), but the idea is nice.Although I still cannot believe it took C++ like 30 years to come up with this.
foo() { return D(_id = "test", _age = "42" }; }
auto r = foo();
std::cout << r.id << r.age << std::endl;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.
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).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.
There are libraries out there that provide syntactic sugar for Common Lisp, which is often discarded because (i) they define their own "ghetto" dialect of Lisp (ii) it is in fact not very important to waste a character or two. Aesthetics alone is not a goal.
For multiple values, you might still use a list instead, in which case you are back to the usual way of accessing data:
(let ((list (multiple-value-list (get-decoded-time))))
(format t "Year: ~A~%Seconds: ~A" (sixth list) (first list)))
If you only want to access a particular value, use "nth-value" (not possible in your example, of course). (let ((struct (my-get-decoded-time)))
(format t "Year: ~A~%Seconds: ~A" (time-year struct) (time-seconds struct)))
With the original multiple value return, if there were a bug and I were reviewing the code, I'd need to also review get-decoded-time and see what order the values are in. If instead it returned structured data, I could skip that step as the values are clear.Lisp is still my favourite language. I generally rank languages by how much time/code I'm spending managing the language versus managing the problem I want to solve. Lisp is very good for that, very little cruft so it lets you focus directly on the problem at hand.
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)))Of course it now also leads to one of Lisp's other problems; namely that after a while everyone ends up programming in their own private language of accumulated hacks :D
1) Learn to deal with a language which has an infinite number of syntactic abstractions.
2) Learn the typical patterns of syntactic abstractions (WITH- , BIND-, DEF- ...).
3) Learn how to write your own abstractions.
Then groups will settle on common (!) Lisp patterns. See for example:
https://common-lisp.net/project/alexandria/
There are lots of libraries which provide language extensions, which are used by many people.
The extremes in Lisp are then:
* no syntactic abstractions -> the power of Lisp wasted
* using those syntactic abstractions which are approved by user groups, due to inclusion into libraries
Above choices are relatively conservative.
As another extreme, it is fully possible to change the language - but then Common Lisp provides more than macros to do so. See for example reader macros, CLOS MOP, customs evaluators/compilers, code walkers, ...
Now I'm trying to make a concerted effort to follow the recommendations here:
http://eudoxia.me/article/common-lisp-sotu-2015/
Hopefully this will lead to a modern set of consolidated libraries. Effectively a new Common Lisp standard: CL-20xx
#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)
The basic getline interface removes heap pressure by reusing the same string over and over, even though semantically the optional<string> approach may look cleaner. Eric Niebler showed a while ago (I can't remember where) that this API lends itself perfectly to ranges, and can provide both a reasonable interface without sacrificing performance. IIRC, the API looked somewhat like this:
for (auto& line : getline_range(cin))
f(line); // do something with line, e.g., parse.
The semantics are: iterate over standard input until EOF or error and let the range keep (and reuse) the string internally while exposing it as const-reference by dereferencing.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";
}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.
The std::unordered_map::insert example in the article is an excellent example of an interface with multiple return values: it returns a pair (iterator, bool) of an iterator to the given key and a bool indicating if the iterator is to a newly inserted key-value pair or if it was the key-value pair already stored in the map. Neither the iterator nor the bool owns a buffer like in the std::getline example, so the std::getline out parameter design is not needed here.
Either way, we do agree on MRV being a language-level special case being a hack. Even PHP isn't that bad — though the unpacking is macro-ish bleh.
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.
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.Generally speaking, taking a secondary value alone feels like a bad use of MVR (it happens rarely with standard functions and most libraries).
MVR are not intended to be substitute to other data-structures. As said elsewhere, you can and should use structs, classes, lists, hash-tables,...
After all, why do we use multiple values instead of a single struct for functions arguments? Because in CL and other languages, unlike in Haskell, you don't want to define an ADT for all your functions or rely on currying. But if you start putting too many coupled parameters in your functions, it starts to smell fishy.
Extracting a single value is easy, by the way:
(nth-value 1 (foo bar baz)) 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...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...
Point is, I disagree, and your opinion is fine. 'Kay?
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
);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.
Not for "int"s, I would be very surprised if sizeof(int*) < sizeof(int) on your system.