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
- The Series
- The Examples
- Constructing a Query to a Database
- Conversion from a String to an Integer
- Conversion from String, More Generic solution
- Monadic Extensions
- Geometry and Intersections
- Simple optional chaining
- Handling a throwing constructor
- Getting File contents
- Haskell’s listToMaybe
- Cleaner interface for map.find
- Configuration of a Nuclear Simulation
- Factory
- Summary
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 - The Complete Guide by Nicolai Josuttis
- C++ Fundamentals Including C++ 17 by Kate Gregory
- C++17 STL Cookbook by Jacek Galowicz
The Examples
Constructing a Query to a Database
Wojciech Razik used optional
to represent possible query parameters:
classQuery{
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_;}
};
voidSelect(constQuery& 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(charconst*const text )-> std::optional<int>
{
char* pos =nullptr;
constint value = std::strtol( text,&pos,0);
return pos == text ? std::nullopt : std::optional<int>( value );
}
int main(int argc,char* argv[])
{
constchar* 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)
{
constauto 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,
typenameFunc,
typenameInput_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;
elsereturn std::invoke(f,*input);
}
And the functions used in the example:
std::optional<std::string> read_file(constchar* 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>
constexprvoid 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(constLine& a,constLine& b){
constauto d1 = a.first - a.second;
constauto d2 = b.first - b.second;
constauto cross = d1.x * d2.y - d1.y * d2.x;
if(std::abs(cross)<1e-6f){// No intersection
return{};
}
constauto x = b.first - a.first;
constauto t1 =(x.x * d2.y - x.y * d2.x)/ cross;
return a.first + t1 * d1;
}
Example use case:
constauto 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{
return0;
}
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>
usingOpt= std::optional<T>;
using std::begin;
// listToMaybe :: [T] -> Opt<T>
template<typename T,template<typename>typenameCont>
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<typenameMap,typenameKey>
auto lookup(constMap& m,constKey& 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(constauto 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.
classParticleRecord
{
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<typenameProduct>
struct basicFactory : boost::noncopyable
{
virtual~basicFactory(){}
virtualbool 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,
typenameRetType,
typenameProduct,
typenameConverter>
class objFactory :public basicFactory<Product>,publicConverter
{
constData::Lookup<T,RetType>* tbl_;
public:
objFactory(constData::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.