Function with a custom return type and the "false" return conditions?

There are three general approaches:

  • Use exceptions. This is what's in Bathsheba's answer.
  • Return std::optional<Cell> (or some other type which may or may not hold an actual Cell).
  • Return bool, and add a Cell & parameter.

Which of these is best depends on how you intend this function to be used. If the primary use case is passing a valid segment, then by all means use exceptions.

If part of the design of this function is that it can be used to tell if a segment is valid, exceptions aren't appropriate, and my preferred choice would be std::optional<Cell>. This may not be available on your standard library implementation yet (it's a C++17 feature); if not, boost::optional<Cell> may be useful (as mentioned in Richard Hodges's answer).

In the comments, instead of std::optional<Cell>, user You suggested expected<Cell, error> (not standard C++, but proposed for a future standard, and implementable outside of the std namespace until then). This may be a good option to add some indication on why no Cell could be found for the segment parameter passed in, if there are multiple possible reasons.

The third option I include mainly for completeness. I do not recommend it. It's a popular and generally good pattern in other languages.


If you can use C++17, another approach would be to use an std::optional type as your return value. That's a wrapper that may or may not contain a value. The caller can then check whether your function actually returned a value and handle the case where it didn't.

std::optional<Cell> CSV::Find(std::string segment) {
  Cell result;
  // Search code here.
  return result;
}

void clientCode() {
   auto cell = CSV::Find("foo");
   if (cell)
      // do stuff when found
   else
      // handle not found
}

The C++ way of dealing with abject failures is to define an exception class of the form:

struct CSVException : std::exception{};

In your function you then throw one of those in the failure branch:

Cell CSV::Find(std::string segment) {
  Cell result;
  // Search code here.
  if (fail) throw CSVException();
  return result;
}

You then handle the fail case with a try catch block at the calling site.

If however the "fail" branch is normal behaviour (subjective indeed but only you can be the judge of normality), then do indeed imbue some kind of failure indicator inside Cell, or perhaps even change the return type to std::optional<Cell>.


Is this function a query, which could validly not find the cell, or is it an imperative, where the cell is expected to be found?

If the former, return an optional (or nullable pointer to) the cell.

If the latter, throw an exception if not found.

Former:

boost::optional<Cell> CSV::Find(std::string segment) {
  boost::optional<Cell> result;
  // Search code here.
  return result;
}

Latter: as you have it.

And of course there is the c++17 variant-based approach:

#include <variant>
#include <string>

struct CellNotFound {};
struct Cell {};

using CellFindResult = std::variant<CellNotFound, Cell>;


CellFindResult Find(std::string segment) {
  CellFindResult result { CellNotFound {} };

  // Search code here.
  return result;
}

template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

void cellsAndStuff()
{
    std::visit(overloaded
    {
        [&](CellNotFound)
        {
            // the not-found code
        },
        [&](Cell c)
        {
            // code on cell found
        }
    }, Find("foo"));
}

Tags:

C++