Quantcast
Channel: Bartek's coding blog
Viewing all articles
Browse latest Browse all 325

A Wall of Your std::optional Examples

$
0
0

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:

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 monadicbind 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(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

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{
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.


Viewing all articles
Browse latest Browse all 325

Trending Articles