C++17 In Detail

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.

C++17 In Detail
© 2017, Bartlomiej Filipek, Blogger platform
Disclaimer: Any opinions expressed herein are in no way representative of those of my employers. All data and information provided on this site is for informational purposes only. I try to write complete and accurate articles, but the web-site will not be liable for any errors, omissions, or delays in this information or any losses, injuries, or damages arising from its display or use.
This site contains ads or referral links, which provide me with a commission. Thank you for your understanding.