Functional in C++17 and C++20

Contents[Show]

Which functional feature can we expect with C++17, for which functional feature can we hope for with C++20? Exactly this question will I concisely answer in this post.

 

With C++17 we get fold expressions and the new container std::optional. Even more thrilling becomes C++20. Concepts lite, the ranges library, and improved futures support totally new concepts in C++.

timeline.FunktionalInCpp17Cpp20Eng

At first to the near future. I will start in the next post with my systematic description of functional programming in C++. Therefore, I will be concise in this post. This post is only intended to make you more appetite.

C++17

Fold expressions

C++11 has Variadic Templates. These are templates that can get an arbitrary number of arguments. The arbitrary number is bound in a parameter pack. New is with C++17 that you can directly reduce a parameter pack with a binary operator. Therefore, you can directly implement Haskell's function family foldl, foldr, foldl1, and foldr1 which reduce a list to a value.

 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
34
35
36
37
38
39
// foldExpression.cpp

#include <iostream>

template<typename... Args>
bool all(Args... args) { return (... && args); }

template<typename... Args>
bool any(Args... args) { return (... || args); }

template<typename... Args>
bool none(Args... args) { return not(... || args); }


int main(){
    
  std::cout << std::endl;

  std::cout << std::boolalpha;

  std::cout << "all(true): " << all(true) << std::endl;
  std::cout << "any(true): " << any(true) << std::endl;
  std::cout << "none(true): " << none(true) << std::endl;
  
  std::cout << std::endl;

  std::cout << "all(true, true, true, false): " << all(true, true, true, false) << std::endl;
  std::cout << "any(true, true, true, false): " << any(true, true, true, false) << std::endl;
  std::cout << "none(true, true, true, false): " << none(true, true, true, false) << std::endl;
  
  std::cout << std::endl;
  
  std::cout << "all(false, false, false, false): " << all(false, false, false, false) << std::endl;
  std::cout << "any(false, false, false, false): " << any(false, false, false, false) << std::endl;
  std::cout << "none(false, false, false, false): " << none(false, false, false, false) << std::endl;
  
  std::cout << std::endl;
  
}

 

The function templates all, any, and none return at compile time true, or false. I will have a closer look at the function template any in line 5 and 6. The parameter pack (...) is unpacked on the binary operator (... && args). The three dots define (ellipse) the parameter pack.

For the output of the program I use the online compiler on cppreference.com.

foldExpressions

Haskell has the maybe monad, C++17 will get std::optional.

std::optional

std::optional stands for the calculation that maybe have a value. A find algorithm or a query of a hash table must deal with the fact that no value is available. Often, you use special values to indicate that you get no result. In short a non-results. Often null-pointers, empty strings, or special integer values are used for non-results. This is inconvenient and error prone because you have to deal special with non-result and you have to distinguish the non-result from a regular result. In case of a non-result you get with std::optional no value. 

 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. The function getFirst will return the first element (line 8), if it exists. If not, a std::optional<int> object. I use in the main function two vectors. The calls getFirst in line 17 and 27 return a std::optional object. The object has in the case of myInt (line 19) a value; in the case myEmptyInt (line 29) no value. Now, I can display the value of myInt (line 20 - 22). The method value_or in line 22 and 30 will return a value, if std::optional-object has a value, if not a default value.

optinal

The impact of functional programming in C++ - in particular in the form of Haskell - increases significantly with C++20. Of course, it's a little big risky to make a prediction about C++20. I made once a mistake and said, that the following features are part of C++17. Which is if course not true.

C++20

Promised, the details to concepts lite, the ranges library, and the improved futures will follow in future posts.

Concepts lite

Type classes in Haskell are interfaces for similar types. If a type is member of a type class, it will have certain properties. Type classes play in generic programming a similar role as interfaces in object-oriented programming. Inspired by type classes, you can specify requirements for the template parameters. The new functionality has the name concepts lite. For example, the sort algorithm requires that is template argument must be sorted.

template<typename Cont>
  requires Sortable<Cont>()
void sort(Cont& container){...}

 

What are the benefits of concepts lite? At first, the template declaration states which properties must hold for the template arguments. Therefore, the compiler can detect the break of the contract and display a clear error message.

std::list<int> lst = {1998,2014,2003,2011};
sort(lst); // ERROR: lst is no random-access container with <

 

The C++ community waits at least as eagerly for the new ranges library from Eric Niebler.

Ranges library

From the birds-perspective, the ranges library empowers you to apply algorithm of the Standard Template Library directly on the whole container. But under the hood, we get totally new programming techniques.

  • Lazy evaluation that enables you to apply algorithm on infinite data streams.
  • Thanks to the pipe symbol we get function composition.
  • Range comprehension supports it to directly create ranges similar to list comprehension in Python or Haskell.
1
2
3
4
  auto odds= view::transform([](int i){ return i*i; }) |
             view::remove_if([](int i){ return i % 2 == 0; }) |
             view::take_while([](int i){ return i < 1000; });
  auto oddNumbers= view::ints(1) | odds;

 

oddNumbers holds as result the square of all odd number that are smaller than 1000: 1, 9, 25, ..., 841, 961. How does it work? The function composition calculates at first to each number is square (line 1), remove all even numbers (line 2), and stops, if the square of numbers are greater than 1000 (line 3). I use odds in line number 4. view::int(1) creates the infinite input stream of integers starting with 0. The input stream will be stopped by odds.

There is the proposal n3721 for the improvement of futures. The main issue with C++11 futures is that you can not compose them. C++20 cleans it up.

Improved futures

The code snippet give you an idea, how the future of std::future will look like.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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));

 

f1.then in line 2 returns a new future that will be executed, if f1 is done with its work. any_f (line 6) will be performed, if one of the futures in line 4 and 5 is done. all_f will be performed, if all ot the futures in line 8 and 9 are done.

One question is still not answered in my post. What have futures with functional programming in common? A lot! The improved futures are a monad. If you don't believe me. Watch: I see a Monad in your Future (Bartosz Milewski). 

What's next?

Now, it's time to start with my systematic. In the next postpost I will answer the question: What is functional programming ?

 

 

 

 

 

 

 

title page smalltitle page small Go to Leanpub/cpplibrary "What every professional C++ programmer should know about the C++ standard library".   Get your e-book. Support my blog.

 


Tags: C++20, C++17

Add comment


Support my blog by buying my E-book

Latest comments

Modernes C++

Subscribe to the newsletter

Including two chapters of my e-book
Introduction and Multithreading

Blog archive

Source Code

Visitors

Today 2119

All 228133

Currently are 124 guests and no members online