打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Returning multiple values from a function in C++ | The Lazy Programmer

Returning multiple values from a function in C++

Filed under: C#,Programming — ferruccio @ 10:22 pm
Tags: , ,

Ideally all functions should return just one value. There are many times, however, when returning more than one value makes a function so much more convenient. The classic example of this convenience is file input. When we read data from a file we want to know two things: Did we reach the end of the file? and if not, what is the next piece of data from the file.

Sometimes, we can encode multiple return values into one. For many of us, the first C idiom we learned from K&R is processing input a character at a time:

int ch;while ((ch = getchar()) != EOF) {    // do character processing...}

This works because the EOF macro was set to something outside the range of valid characters (usually -1). While this approach can work fairly well for simple cases, it quickly breaks down as the types we wish to return get more complex.

One way to return multiple values is to return one value through the function return mechanism and to pass pointers or references to objects which will receive the additional parameters as additional arguments to the function. For the sake of example, we are going to create a simple function which returns a single line of text from an input stream. This function will take one input parameter, the input stream, and return two values. The first value will be a boolean to indicate success and the second value will be the string that was read. Using the approach just described, we could code it up as:

bool readline(istream& is, string& line){    if (is.eof())        return false;    getline(is, line);    return true;}

And we would call it like this:

string line;while (readline(ss, line))    cout << line << endl;

So, what’s wrong with this approach?

There’s a couple of issues.

  1. The first issue is mostly aesthetic. When we write functions, the output values are on the left and the input values go on the right. Now we have output values as part of the parameter list. We can document the function to indicate that line is an output parameter, but we aren’t going to document every call.
  2. The second issue is that because all the output values are independent of each other, there is no way, at compile time, to enforce that every output parameter has been set to something. If you forgot to write a return statement in a function, the compiler will let you know. It would be nice to have the same error checking for all output values.

Another approach is to return a struct which contains all the return values. Our line reading function now becomes:

struct line_ret{    bool    success;    string  line;};line_ret readline(istream& is){    line_ret lr = { false, "" };    if (!is.eof()) {        lr.success = true;        getline(is, lr.line);    }    return lr;}

And we would call it with:

for (;;) {    line_ret lr = readline(ss);    if (!lr.success) break;    cout << lr.line << endl;}

At first this seems like a reasonable approach. The problem here is that we needed to define a struct whose only use is to represent the return values from this function. Structs should be used to hold logically related items. A good rule of thumb is if a struct (or class, obviously) doesn’t already exist, don’t create it just to contain multiple return values.

The last approach (and the whole point of this post) is to use tuples. I am going to demonstrate the same example using the Boost.Tuple library but, if your compiler supports it, you could just as easily use TR1 tuples. They are the same thing. Using tuples, our code now looks like:

#include <boost/tuple/tuple.hpp>using namespace boost;tuple<bool,string> readline(istream& is){    if (is.eof()) return tuple<bool,string>(false);    string line;    getline(is, line);    return tuple<bool,string>(true, line);}

This version returns a tuple which consists of a bool an a string. Note that the failure case has only the boolean in the return statement. Since the string is not provided, the default string constructor will be called to create an empty string which will be returned. We could call this function like this:

for (;;) {    tuple<bool,string> ret = readline(ss);    if (!ret.get<0>()) break;    cout << ret.get<1>() << endl;}

The get<n>() syntax returns the nth member of a tuple. While this works, it’s not very clear. Fortunately, you can use a tier (pronounce it “tire” instead of “tear” and the name makes much more sense) to help out with that:

for (;;) {    bool success;    string line;    tie(success, line) = readline(ss);    if (!success) break;    cout << line << endl;}

The tier ties success with the first tuple item and line with the second tuple item. That makes the resulting code much easier to read and we get compile-time checking for all the values we’re returning.

4 Comments

  1. Hi Ferruccio,

    2 comments:

    1) There is an error in all versions of readline() in your post: Testing a stream for eof() is not a guarantee of success. The stream could be in another fail state. Checking the return value of getline would be safer (and more idiomatic). In your tuple-returning version, that would give something like:

    string line;bool success = getline(is, line);return make_tuple(success, line);

    2) The Boost.Optional (http://www.boost.org/doc/libs/1_37_0/libs/optional/doc/html/index.html) library does something very similar, with added syntactic sugar:
    Definition of readline:

    optional<string> readline(istream& is){  optionall<string> result;  string line;  if (getline(is, line)){    result = line;  }    return result; }

    Use:

    while(optionall<string> s = readline(ss)){  cout l<l< s l<l< endl;}

    (Note: none of the code in this comment was tested, or even compiled. It is only OTOMH)

    Comment by éric Malenfant — January 6, 2009 @ 6:14 pm

  2. éric,

    Yes, of course, you’re right. This was a trivial example designed to show how to return multiple values. A more realistic example might be a function that returns a couple of types not expressible by using boost.optional (or returned three value), but I wanted to keep the example simple.

    Thanks for the reminder about optional. That is a very handy way to have nullable types in C++.

    – Ferruccio

    Comment by Ferruccio — January 6, 2009 @ 7:54 pm

  3. Hi.

    First of all, nice blog!

    I like tuples but in this case I think a simple STL pair suffices.

    About getchar() and similar functions.
    Steve Maguire in his old “Writing Solid Code” book is pretty explicit: they should return just a “flag” (boolean) while putting the result in a pointer-type argument allocated by the caller.

    This should make also the function a bit more reentrant.

    jp

    Comment by jp — April 21, 2009 @ 2:41 pm

  4. Various of guys write about this issue but you wrote down really true words.

    Comment by Clidgejed — December 11, 2009 @ 11:09 pm

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
Python Variable Types
Chris Smith‘s completely unique view : F# in 20 Minutes – Part II
Redis PHP通用类
开源代码
Write - Function Summary
C# 中的 @ 符号的使用及注意事项
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服