optional

Monads in C++

Monads in C++? What a strange name for a post. But it’s not so strange. With std::optional, C++17 gets a monad. The ranges library from Eric Niebler and the extended futures are also monads. For both, we can hope for in C++20.

Bjarne Stroustrup presented in his Secret Lightning Talk at the Meeting C++ 2016 a few concepts of Concepts Lite that we will get with high probability in C++20. There were also mathematical concepts such as ring and monad. My assumption becomes more and more reality. Modern C++ will be hardened for the future.

std::optional

Haskell’s Maybe Monad inspires std::optional. std::optional was initially intended to be part of C++14 stands for a calculation that maybe has a value. Therefore, a find algorithm or a hash table query has to deal with the fact that the question can not be answered. Often, you use particular values that stand for the presence of no value, the so-called no result. Often we use a null pointer, empty strings of particular integer values for no results. This technique is expensive and error-prone because you have to deal with the no-results especially. No results are of the same type as regular results. std::optional has in case of a no-result no value.

Here is a short example.

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// optional.cpp

#include <experimental/optional>
#include <iostream>
#include <vector>

std::experimental::optional<int> getFirst(const std::vector<int>& vec){
  if (!vec.empty()) return std::experimental::optional<int>(vec[0]);
  else return std::experimental::optional<int>();
}

int main(){
    
    std::vector<int> myVec{1, 2, 3};
    std::vector<int> myEmptyVec;
    
    auto myInt= getFirst(myVec);
    
    if (myInt){
        std::cout << "*myInt: "  << *myInt << std::endl;
        std::cout << "myInt.value(): " << myInt.value() << std::endl;
        std::cout << "myInt.value_or(2017):" << myInt.value_or(2017) << std::endl;
    }
    
    std::cout << std::endl;
    
    auto myEmptyInt= getFirst(myEmptyVec);
    
    if (!myEmptyInt){
        std::cout << "myEmptyInt.value_or(2017):" << myEmptyInt.value_or(2017) << std::endl;
    }
    
}

 

std::optional is currently in the namespace experimental. That will change with C++17. I use std::optional in the function getFirst (line 7). getFirst returns the first element if it exists (line 8). You will get a std::optional<int> object (line 9) if not. I use in the main function two vectors. The calls get first in lines 17 and 27 return the std::optional objects. In the case of myInt (line 19), the object has a value; in the case of myEmptyInt (Zeile 29), the object has no value. Now I can display the value of myInt (lines 20 – 22). The method value_or in lines 22 and 30 returns the value or a default value. This is because std::optional has a value.

 

The screenshot shows the output of the program using the online compiler at cppreference.com

Rainer D 6 P2 500x500

 

Rainer D 6 P2 500x500Modernes C++ Mentoring

  • "Fundamentals for C++ Professionals" (open)
  • "Design Patterns and Architectural Patterns with C++" (open)
  • "C++20: Get the Details" (open)
  • "Concurrency with Modern C++" (open)
  • "Generic Programming (Templates) with C++": October 2024
  • "Embedded Programming with Modern C++": October 2024
  • "Clean Code: Best Practices for Modern C++": March 2025
  • Do you want to stay informed: Subscribe.

     

    optional

    Extended futures

    Modern c++ supports tasks.

     tasksEng

     

    Tasks are pairs of  std::promise and std::future objects connected by a channel. Both communication endpoints may exist in different threads. The std::promise (sender) pushes its value into the channel for which the std::future (receiver) is waiting. The sender can push a value, a notification, or an exception into the channel. I’ve written a few posts about tasks. Here are the details:  Tasks.

    The easiest way to create a promise is to use the function std::async. std::async behaves like an asynchronous function call.

    int a= 2000
    int b= 11;
    std::future<int> sum= std::async([=]{ return a+b; });
    std::cout << sum.get() << std::endl;
    

     

    The call std::async performs more actions. First, it creates the communication endpoints’ promise and future; second, it connects them via a channel. The lambda function [=]{ return a+b;} is the work package of the promise. It captures arguments a and b from their defining context. The C++ run time decides if the promise will run in the same or a different thread. Criteria for its decision may be the size of the work package, the load of the system, or the number of cores.

    The future calls sum.get() to get the value from the promise. You can only once call sum.get(). If the promise is not made, the get call will block.

    Tasks provide similar and safer handling of threads because they have no shared state that has to be protected. Therefore, race conditions are not possible, and deadlocks are much rarer. But, the C++11 implementation of futures has a big disadvantage. The composition of std::future objects is not possible. This will not hold true for the extended futures of C++20.

    The table shows the functions for extended futures.

    futureImprovementEng

    Here are a few code snippets from proposal n3721.

     

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    future<int> f1= async([]() {return 123;});
    
    future<string> f2 = f1.then([](future<int> f) {
         return f.get().to_string(); 
    });
    
    future<int> futures[] = {async([]() { return intResult(125); }), 
                             async([]() { return intResult(456); })};
    
    future<vector<future<int>>> any_f = when_any(begin(futures), end(futures));
    
    
    future<int> futures[] = {async([]() { return intResult(125); }), 
                             async([]() { return intResult(456); })};
    
    future<vector<future<int>>> all_f = when_all(begin(futures), end(futures));
    

     

    The future f2 in line 3 is ready if the future f2 is ready. You can enlarge the chain of futures:  f1.then(…).then(…).then(…). The future any_f in line 10 becomes ready if any of its futures become ready. On the contrary, the future all_f in line 16 becomes ready if all its futures become ready.

    One question is still not answered. What have futures in common with functional programming? A lot! The extended futures are a monad. I explained in the post Pure Functions the idea of monads. The key idea of a monad is that a monad encapsulates a simple type in an enriched type and supports the compositions of functions on these enriched types. Therefore, the monad needs a function to lift the simple type into an enriched one. Additionally, a monad needs a function that empowers them to compose functions on enriched types. This job is for the functions make_ready_future, then, and future<future<T>>. make_ready_future maps a simple type into an enriched type, a so-called monadic value. This function is called identity and has the name return in Haskell. The two functions then and future<future<T>> are equivalent to the bind operator in Haskell. The bind operator’s job is to transform one monadic value into another monadic value. bind is the function composition in a monad. 

    Thanks to the method when_any std::future even becomes a Monad Plus. A Monad Plus requires from its instances that they are monads and have an operator msum. Therefore, std::future supports a kind of addition operation in C++20.

    If you want to know the details, you should read the excellent blog of Bartosz Milelweski and watch his video:  “C++17: I See a Monad in Your Future!”.

    What’s next?

    In my post Recursion, List Manipulation, and Lazy Evaluation, I wrote: The story about lazy evaluation in C++ is quite short. But I made my conclusion without templates. Thanks to the CRTP idiom and expression templates C++ is lazy. Therefore, I will write about the infamous CRTP idiom in the next post.

     

     

     

     

     

     

     

    Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Stephen Kelley, Kyle Dean, Tusar Palauri, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, Marco Parri Empoli, Philipp Lenk, Charles-Jianye Chen, Keith Jeffery, Matt Godbolt, and Honey Sukesan.

    Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.

    My special thanks to Embarcadero
    My special thanks to PVS-Studio
    My special thanks to Tipi.build 
    My special thanks to Take Up Code
    My special thanks to SHAVEDYAKS

    Modernes C++ GmbH

    Modernes C++ Mentoring (English)

    Do you want to stay informed about my mentoring programs? Subscribe Here

    Rainer Grimm
    Yalovastraße 20
    72108 Rottenburg

    Mobil: +49 176 5506 5086
    Mail: schulung@ModernesCpp.de
    Mentoring: www.ModernesCpp.org

    Modernes C++ Mentoring,

     

     

    0 replies

    Leave a Reply

    Want to join the discussion?
    Feel free to contribute!

    Leave a Reply

    Your email address will not be published. Required fields are marked *