When designing a new API one of the things I put a lot of thought into is how the user will know if the API call was successful or not. I don’t want to levy large error checking requirements on my users, but in C/C++ you can only return a single data type, so many APIs will pass the real output back through a referenced argument in the function prototype and a simple Boolean or error code as the return value. I find this clunky and hard to document, so I dug my heels in to find a better way.

std::tuple and std::tie are two useful C++ features that can help you return multiple values from a function. std::tuple is a container that holds a tuple of values, while std::tie allows you to tie objects together so that they can be accessed as if they were one object. In this post, we’ll take a look at how to use these two features to make returning multiple values from a function easier.

std::tuple and std::pair

std::tuple (and std::pair) are C++ templates that allow you to combine two or more objects together and pass them around as if they were one. They are the clear choice for combining multiple outputs from a function into a single return data type. This creates clean, self-documenting code that is easy for a user to understand and follow.

For example, let’s say we were dealing with a factory that created our objects. We’d have a creation method that looks something like this:

std::shared_ptr<Object> MyFactory::create()
{
  return std::make_shared<Object>();
}
Code language: C++ (cpp)

One shortcoming here is that the create function does not to any error checking whatsoever, putting the entire burden on the user.

A (slightly) improved version of the create method could be:

bool MyFactory::create(std::shared_ptr<Object> &p)
{
  p = std::make_shared<Object>();
  if (!p) return false;
  return true;
}
Code language: C++ (cpp)

The user can now easily check the return value to determine whether or not the object was created successfully and perform additional processing. However, now they have to create the shared_ptr<T> object before calling the create function; and in addition to that, they also have to understand the argument p is not an input parameter, but rather they output parameter they are after in the first place.

Instead, let’s make use of a std::pair to return both the created object as well as whether creation was successful.

std::pair<std::shared_ptr<Object>, bool> MyFactory::create()
{
  auto p = std::make_shared<Object>();
  return std::make_pair(p, !!p); // !!p same as static_cast<bool>(p) or 'if (p)'
}
Code language: C++ (cpp)

How is this better? You may look at this and think that the user still has to grab the success value from the pair and that is absolutely correct. In this trivial case, creation is just a couple of lines. However, in a real-world scenario your function will likely be much more complex with many more errors to handle. Now, instead of levying that requirement on your user, you have captured all the error handling logic (and maybe reporting) internally. The user just has to check whether the returned data is valid or not via the Boolean in the pair.

You can also just as easily extend this to return multiple values in a std::tuple:

std::tuple<UUID, std::string, bool> createMsg(const std::string &msg, const int id)
{
  UUID uuid = makeNewUUID();
  std::string outputmsg = std::to_string(id) + ": " + msg;
  return std::make_tuple(uuid, outputmsg, isUUIDValid(uuid));
}
Code language: C++ (cpp)

Using std::tie to Access Return Values

To access multiple return values, std::tie comes to the rescue. std::tie “ties” variable references together into a std::tuple. Accessing your multiple return values becomes straightforward at this point:

// Factory Example
std::shared_ptr<Object> obj;
bool objvalid{false};
std::tie(obj, objvalid) = MyFactory::create();
if (objvalid) obj.work();

// Message Example
UUID lUuid;
std::string msg;
bool msgvalid{false};
std::tie(lUuid, msg, msgvalid) = createMsg("Test message", 73);
if (msgvalid) std::cout << lUuid << ": " << msg << std::endl;
Code language: C++ (cpp)

Conclusion

The C++ Core Guidelines make it clear that passing back a std::tuple (or std::pair) is the preferred way to return multiple return values. It is also clear that if there are specific semantics to your return value that a class or structure object is best.

std::tuple and std::pair provide a nice way to return multiple values from a function without having to resort to ugly workarounds. By using std::tie, we can make receiving the return value a breeze. What do you think? How will you use this in your next project?

Last modified: January 30, 2023

Author

Comments

Write a Reply or Comment

Your email address will not be published.