Last Update:
A Wall of Your std::optional Examples
Table of Contents
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:
- Refactoring with
std::optional
- Using
std::optional
- Error handling and
std::optional
- About
std::variant
- Using
std::any
- In place construction for
std::optional
,std::variant
andstd::any
- Using
std::string_view
- C++17 string searchers & conversion utilities
- Working with
std::filesystem
- Something more? :)
- Show me your code: std::optional
- Optional Contest Results - This post!
Resources about C++17 STL:
- C++17 In Detail by Bartek!
- C++17 - The Complete Guide by Nicolai Josuttis
- C++ Fundamentals Including C++ 17 by Kate Gregory
- Practical C++14 and C++17 Features - by Giovanni Dicanio
- C++17 STL Cookbook by Jacek Galowicz
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 forExpected
.
Geometry and Intersections
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
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.
I've prepared a valuable bonus if you're interested in Modern C++!
Learn all major features of recent C++ Standards!
Check it out here: