Partners: KDAB Whole Tomato Software CppDepend

11 June 2018

A Wall of Your std::optional Examples

std::optional contest

Two weeks ago I asked you for help: I wanted to build a wall of examples of std::optional. I’m very grateful that a lot of you responded and I could move forward with the plan!

You’re amazing!

Let’s dive in the examples my readers have sent me!

A Reminder

To remind, I asked for some real-life examples of std::optional. It’s exciting to see in how many ways you use this vocabulary type in your projects. There are many options and variations. In this post, I’ve put all of them in a single place.

Most of the code is as I got it from the authors, in some places I had to shorten it and extract only the core parts.

Giveaway

For this experiment, I also had 2 x 25$ Amazon.com Gift Card. I randomly selected two participants, and I’ve contacted them already :)

I wonder if they spend that enormous amount of money on some C++ book or a course :)

The Series

This article is part of my series about C++17 Library Utilities. Here’s the list of the other topics that I’ll cover:

Resources about C++17 STL:

The Examples

Constructing a Query to a Database

Wojciech Razik used optional to represent possible query parameters:

class Query {
    std::optional<int> limit_;
    std::optional<std::string> name_;
    // ... more params
public:
    Query& Limit(int l)        { limit_ = l;           return *this;}
    Query& Name(std::string s) { name_ = std::move(s); return *this;}

    std::optional<int> GetLimit() const { return limit_;}
    std::optional<std::string> GetName() const { return name_; }
};

void Select(const Query& q) { // couts for demonstration only
    std::cout << " - \n";
    if (q.GetLimit()) { 
        std::cout << "Limit: " << q.GetLimit().value() << "\n";
    }
    if (q.GetName()) {
        std::cout << "Name: " << q.GetName().value() << "\n";
    }
}

int main() {
    Select(Query{}.Name("Some name"));
    Select(Query{}.Limit(3));
    // You can find objects with empty fields!
    Select(Query{}.Limit(5).Name("")); 
}

Play with the code @Coliru

I like the idea of chaining to build the final query object.

Conversion from a String to an Integer

In the following example, Martin Moene applied std::optional to a function that converts strings to integers.

auto to_int( char const * const text ) -> std::optional<int>
{
    char * pos = nullptr;
    const int value = std::strtol( text, &pos, 0 );

    return pos == text ? std::nullopt : std::optional<int>( value );
}

int main( int argc, char * argv[] )
{
    const char * text = argc > 1 ? argv[1] : "42";

    std::optional<int> oi = to_int( text );

    if ( oi ) std::cout << "'" << text << "' is " << *oi;
    else      std::cout << "'" << text << "' isn't a number";
}

Alternatively with more compact code:

if ( auto oi = to_int( text )) 
    std::cout << "'" << text << "' is " << *oi;
else
    std::cout << "'" << text << "' isn't a number";

Play with the code @Wandbox

Conversion from String, More Generic solution

jft went a bit further with the previous idea of string conversions and wrote a function that uses istringstream to convert to many different numeric types.

// Converts a text number to specified type. 
// All of the text must be a valid number of the specified type. 
// eg 63q is invalid
// Defaults to type int
// st - string to convert
// returns either value of converted number or 
// no value if text number cannot be converted

template<typename T = int>
std::optional<T> stonum(const std::string& st)
{
    const auto s = trim(st);
    bool ok = s.empty() ? 
                 false : (std::isdigit(s.front()) 
                       || (((std::is_signed<T>::value 
                       && (s.front() == '-')) 
                       || (s.front() == '+')) 
                       && ((s.size() > 1) 
                       && std::isdigit(s[1]))));

    auto v = T {};

    if (ok) {
        std::istringstream ss(s);

        ss >> v;
        ok = (ss.peek() == EOF);
    }

    return ok ? v : std::optional<T> {};
}

// use case:
string snum = "42.5";
if (auto n = stonum<double>(snum); n.has_value())
    cout << snum << " is double " << *n << endl;
else
    cout << snum << " is not a double" << endl;

Play with the code @Coliru

std::istream::operator>> has overloads for many numeric types, so with this one handy function you can potentially have a converter to many types from a string.

Monadic Extensions

This snippet comes from Lesley Lai

Full code @Gist

The basic idea is to be able to chain operations that return std::optional.

auto x = read_file("exist.txt")
         >> opt_stoi
         >> [](int n) { return std::make_optional(n + 100); };
print(x);

This is done by clever overloading of >>.

template<typename T1,
         typename Func,
         typename Input_Type = typename T1::value_type,
         typename T2 = std::invoke_result_t<Func, Input_Type>
         >
constexpr T2 operator>>(T1 input, Func f) {
    static_assert(
                std::is_invocable_v<decltype(f), Input_Type>,
                "The function passed in must take type" 
                "(T1::value_type) as its argument"
                );

    if (!input) return std::nullopt;
    else return std::invoke(f, *input);
}

And the functions used in the example:

std::optional<std::string> read_file(const char* filename) {
    std::ifstream file {filename};

    if (!file.is_open()) {
        return {};
    }

    std::string str((std::istreambuf_iterator<char>(file)),
                     std::istreambuf_iterator<char>());
    return {str};
}


std::optional<int> opt_stoi(std::string s) {
    try {
        return std::stoi(s);
    } catch(const std::invalid_argument& e) {
        return {};
    } catch (const std::out_of_range& ) {
        return {};
    }
}

template <typename T>
constexpr void print(std::optional<T> val) {
    if (val) {
        std::cout << *val << '\n';
    } else {
        std::cerr << "Error\n";
    }
}

Play with the code @Coliru

And the notes from the author:

This snippet implement monadic bind operation that chain functions together without explicitly checking errors. The whole gist is inspired by Phil Nash’s talk at North Denver Metro C++ Meetup C++ meetup.

I use optional here because it is in the standard library, Expected should fit the error handling job better since it stores information about why an error happened. I do not think the implementation of this function will change for Expected.

Geometry and Intersections

by Arnaud Brejeon

Full code @Gist

The original code is much longer and uses operator overloading, plus a separate type declaration Point and Line, but it should be clear what the code does:

std::optional<Point> intersection(const Line& a, const Line& b) {
    const auto d1 = a.first - a.second;
    const auto d2 = b.first - b.second;
    const auto cross = d1.x * d2.y - d1.y * d2.x;

    if (std::abs(cross) < 1e-6f) { // No intersection
        return {};
    }

    const auto x = b.first - a.first;
    const auto t1 = (x.x * d2.y - x.y * d2.x) / cross;
    return a.first + t1 * d1;
}

Example use case:

const auto i0 = intersection(
                       Line(Point(-1, 0), Point(1, 0)), 
                       Line(Point(0, -1), Point(0, 1))
                 );

std::cout << std::boolalpha << i0.has_value();

if(i0) {
    std::cout << " : " << i0->x << ", " << i0->y;
}

Simple optional chaining

by Jeremiah O’Neil

While we can chain optional in many ways, Jeremiah showed a simple way:

int a = //value one;
int b = //value two;

if (optional<int> tmp, x;
    (tmp = fa(a)) && (x = fb(b)) && (x = fcd(*tmp, *x)) && (x = fe(*x)))
{
    return *x;
} else {
    return 0;
}

Each of the functions fa, fb, fcd, fe (what awesome names!) returns std::optional. But thanks to the short circuit rules and the evaluation happening from left to right the functions won’t be executed if the previous one fails (when a function returns nullopt.

Play with the code @Coliru

Handling a throwing constructor

Edoardo Morandi managed to wrap a throwing constructor into a wrapper class that instead of throwing allows you to check if the object is initialised or not.

Full code @Compiler Explorer

// A simple struct, without anything special related to exception handling
struct S_impl {
    S_impl() = default;

    // This can throw!
    S_impl(std::size_t s) : v(s) {}

    std::vector<double>& get() { return v; }

private:
    std::vector<double> v;
};

// A (too) simple user interface for S_impl
struct S : std::optional<S_impl> {
    template<typename... Args>
    // A `noexcept` wrapper to construct the real implementation.
    S(Args&&... args) noexcept : 
        optional<S_impl>(
            // Construct std::optional inplace using constructor initialization,
            // leading to pre-C++20 ugly code to universal forwarding :(
            [args = std::tuple<Args...>(std::forward<Args>(args)...)]() mutable {
                return std::apply([](auto&&... args) -> std::optional<S_impl> {
                    try {
                        return std::optional<S_impl>(std::in_place, std::forward<Args>(args)...);
                    } catch (...) {
                        return std::nullopt;
                    }
                }, std::move(args));
            }()
        )

    {
    }
};

The code converts a class with a throwing constructor to a wrapper class that won’t throw. Such wrapper derives from std::optional<T> so you can directly check if the value is there or not.

Getting File contents

by Michael Cook

full code @Coliru

std::optional<std::string> 
get_file_contents(std::string const& filename)
{
  std::ifstream inf{filename};
  if (!inf.is_open())
    return std::nullopt;
  return std::string{std::istreambuf_iterator<char>{inf}, {}};
}

int main()
{
  if (auto stat = get_file_contents("/proc/self/stat"))
    std::cout << "stat " << *stat << '\n';
  else
    std::cout << "no stat\n";

  if (auto nsf = get_file_contents("/no/such/file"))
    std::cout << "nsf " << *nsf << '\n';
  else
    std::cout << "no nsf\n";
}

Haskell’s listToMaybe

From Zachary

Full code @Compiler Explorer

template <typename T>
using Opt = std::optional<T>;

using std::begin;

// listToMaybe :: [T] -> Opt<T>
template <typename T, template <typename> typename Cont>
auto listToMaybe( Cont<T> const& xs ) -> Opt<T>
{
   return xs.empty() ? Opt<T>{} : Opt<T>{ *( begin( xs ) ) };
}

auto f()
{
  auto as = std::vector<int>{};
  std::cout << listToMaybe( as ).value_or( 0 ) << '\n'; // 0
}

Haskell listToMaybe documentation.

Cleaner interface for map.find

Vincent Zalzal make a simple, yet handy extension to .std::map Rather than checking for map::end you can use optional.

the full code @Coliru

// Provide an iterator-free interface for lookups to map-like objects.
// Warning: the output value is copied into the optional.
template <typename Map, typename Key>
auto lookup(const Map& m, const Key& k)
{
    auto it = m.find(k);
    return it != m.end()
               ? std::make_optional(it->second) 
               : std::nullopt;
}

int main()
{
    const std::map<int, int> squares = { {1, 1}, {2, 4}, {3, 9}, {4, 16} };

    // cleaner, no need for != end()
    if (const auto square = lookup(squares, 2))
    {
        std::cout << "Square is " << *square << '\n';
    }
    else
    {
        std::cout << "Square is unknown.\n";
    }
}

Comparing against map::end is sometimes ugly, so wrapping the search into optional looks nice.

I wonder if there are plans to apply optional/variant/any to API in STL. Some overloads would be an excellent addition.

Configuration of a Nuclear Simulation

This comes from Mihai Niculescu who used optional in the configuration of a nuclear simulator.

class ParticleRecord
{
    friend std::istream& operator>> (std::istream& is, 
                                     ParticleRecord& p);
public:
    double x() const { return x; }
    double y() const { return y; }
    double z() const { return z; }
    double px() const { return px; }
    double py() const { return py; }
    double pz() const { return pz; }
    double mass() const { return mass; }

    const std::optional<extendedInfo>& extendedInfo() const 
    { return extendedData; }

private:
    void setExtended(double tdecay, double tformation, long uniqueId)
    {
        extendedInfo einfo;
        einfo.tdec = tdecay;
        einfo.tform= tformation;
        einfo.uid = uniqueId;

        extendedData = einfo;
    }

    double x, y, z; // position (x,y,z)
    double px, py, pz; // momentum (px, py, pz)
    double mass; //  mass

    // extended data is available when Sim's parameter 13 is ON
    std::optional<extended_fields> extendedData; 
};

A natural choice for values that might not be available. Here, if the extendedData is loaded, then the simulation will behave differently.

Factory

This comes from Russell Davidson.

using namelist = std::vector<std::string>;

template <typename Product>
struct basicFactory : boost::noncopyable
{
  virtual ~basicFactory() {}
  virtual bool canProduce(const std::string& st) const = 0;
  virtual std::optional<Product> produce(const std::string& st)
     const = 0;
  virtual namelist keys() const = 0;
};

template <typename T,
         typename RetType,
         typename Product,
         typename Converter>
class objFactory : public basicFactory<Product>, public Converter
{
  const Data::Lookup<T, RetType>* tbl_;

public:
  objFactory(const Data::Lookup<T, RetType>* tbl) : tbl_(tbl) {}
  bool canProduce(const std::string& key) const
  { 
      return tbl_->ismember(key); 
  }

  std::optional<Product> produce(const std::string& key) const
  {
     RetType ret = tbl_->find(key);
     if (!ret) return std::nullopt;
     return std::make_optional<Product>(Converter::convert(ret));
  }

  namelist keys() const { return tbl_->keys(); }
};

The key method is std::optional<Product> produce(const std::string& key) const which returns a created Products or nullopt.

Summary

Once again thanks for all of the submissions. There are many ways how you can use a particular helper type - in this case std::optional. By looking at real-life examples, you can hopefully learn more.

Do you have any comments regarding the examples? Would you suggest some changes/improvements? Let us know.

Get my free ebook about C++17!

More than 50 pages about the new Language Standard.

C++17 in detail, by Bartlomiej Filipek

© 2017, Bartlomiej Filipek, Blogger platform
Any opinions expressed herein are in no way representative of those of my employers.
This site contains ads or referral links, which provide me with a commission. Thank you for your understanding.