Concepts in C++20: An Evolution or a Revolution?

Let me conclude my series to concepts with this meta-post. Are concepts an evolution or a revolution in C++? The answer to this question bothered me quite a time.

 TimelineCpp20Concepts

I assume we all know what evolution or revolution means but let me be more precise. These definitions from re:invention are quite concise:

  • Evolution is defined as gradual change, adaptation, progression, metamorphosis.
  • Revolution is defined as forcible overthrow for an entirely new system…drastic, disruptive, far-reaching, momentous change.

To make it short. The crucial difference between evolution and revolution is if the change is gradual (evolution) or disruptive (revolution).

I had many discussions in the series to concepts. Consequentially, I was curious about what your opinion to concepts is. The answers I got are in German. For your convenience, I paraphrase it in English. Interestingly, my readers have a clear tendency to evolution. Honestly, I'm more on the revolution side.

Which argument speaks for evolution and which argument speaks for revolution?

Evolution

evolution 4107273 1280

Image by Alexas_Fotos from Pixabay

Clear Abstraction 

When used properly, concepts should promote clean working with generic code at a higher level of abstraction.

In the longer term, the standard concepts should become increasingly idiomatic in C++. The interoperability and modular work, especially in larger teams, can be made more robust and less prone to errors, if more on abstract properties of the parameter classes is checked and less for purely syntactic "rollout" in generic code.

Simple Definition and meaningful Error Messages

You cannot do anything that you would not have been able to do with type-traits library, SFINAE and static_assert so far, even if it was very cumbersome and time-consuming. Their advantages lie in the simple definition and meaningful error messages.

Unconstrained Placeholders

Since C++11 we have auto to deduce the type of a variable from its initializer.

auto integ = add(2000, 11);

std::vector<int> myVec{1, 2, 3};
for (auto v: myVec) std::cout << v << std::endl;

auto is a kind of unconstrained placeholder. With C++20, we can use concepts as constrained placeholders.

 

template<typename T>                                   
concept Integral = std::is_integral<T>::value;

Integral auto integ = add(2000, 11);

std::vector<int> myVec{1, 2, 3};
for (Integral auto v: myVec) std::cout << v << std::endl;

 

To make it concise and evolutionary: you can use a constrained placeholder (concept) in each place you could use an unconstrained placeholder (auto).

Generic Lambdas

 With C++14 you can use a generic lambda (addLambda) which becomes essentially a function template (addTemplate).

// addLambdaGeneric.pp

#include <iostream>

auto addLambda = [](auto fir, auto sec){ return fir + sec; }; 

template <typename T, typename T2>                            
auto addTemplate(T fir, T2 sec){ return fir + sec; }


int main(){
    
    std::cout << addLambda(2000, 11.5);    // 2011.5
    std::cout << addTemplate(2000, 11.5);  // 2011.5
    
}

 

Using auto in a function declaration was not possible with C++14. Since C++20 you can use a constrained placeholder (concept) or an unconstrained placeholder (auto) in a function declaration and the function declaration becomes a function template with unconstrained (auto) or constrained (concept) parameters.

// addUnconstrainedConstrained.cpp

#include <concepts>
#include <iostream>

auto addUnconstrained(auto fir, auto sec){
     return fir + sec;
}

std::floating_point auto addConstrained(std::integral auto fir, 
                                        std::floating_point auto sec){
     return fir + sec;
}

int main(){
    
    std::cout << addUnconstrained(2000, 11.5); // 2011.5
    std::cout << addConstrained(2000, 11.5);   // 2011.5
    
}

 

I intentionally used a strange signature for my addConstrained function to make my point. 

Revolution

france 63022 1280

 

Image by WikiImages from Pixabay

Template Requirements are verified

Admittedly, you can specify template requirements in C++11 in the template declaration.

// requirementsCheckSFINAE.cpp

#include <type_traits>

template<typename T,
         typename std::enable_if<std::is_integral<T>::value, T>:: type = 0>
T moduloOf(T t) {
    return t % 5;
}

int main() {

    auto res = moduloOf(5.5);

}

 

The function template moduloOf requires, that the T has to be integral. If T is not integral and, therefore, the expression std::is_integral<T>::value evaluates to false, the failed substitution is not an error. The compiler removes the overload from the set of all potential overloads. After that step, there is no valid overload left.

SFINAE

This technique is called SFINAE and stands for Substitution Failure Is Not An Error. 

Honestly, I only teach this technique in template classes. This does not hold for concepts. The issue becomes immediately obvious.

// requirementsCheckConcepts.cpp

#include <concepts>

std::integral auto moduloOf(std::integral auto t) {
    return t % 5;
}

int main() {

    auto res = moduloOf(5.5);

}

 

 conceptsError

 

Definition of Templates radically improved

Thanks to the abbreviated function template syntax, the definition of a function template becomes a piece of cake. I already presented the new syntactic sugar in the function declarations of addConstrained, and moduloOf. Therefore, I skip the example.

Semantic Categories

Concepts do not stand for syntactic constraints but for semantic categories. 

Addable is a concept that stands for a syntactic constraint.

 

template<typename T>
concept Addable = has_plus<T>;    // bad; insufficient

template<Addable N> auto algo(const N& a, const N& b) // use two numbers
{
    // ...
    return a + b;
}

int x = 7;
int y = 9;
auto z = algo(x, y);   // z = 16

std::string xx = "7";
std::string yy = "9";
auto zz = algo(xx, yy);   // zz = "79"

 

I assume Addable behaves not like expected. The function template algo should accept arguments which model numbers and not just support the + operator. Consequentially, two strings can be used as arguments. This is bad because addition is commutative, but not string concatenation: "7" + "9" != "9" + "7".

The solution is quite simple. Define the concept Number. Number is a semantic category such as Equal, Callable, Predicate, or Monad are.

My Conclusion

Of course, many arguments speak for an evolutionary step or a revolutionary jump with concepts. Mainly because of the semantic categories I'm on the revolution side. Concepts such as Number, Equality, or Ordering remind me of Platon's word of ideas. It is revolutionary for me that we can now reason about programming in such categories. 

What's next?

The ranges library, that I present in the next post, is the first customer of concepts. Ranges supports algorithms which can

  • operate directly on the container
  • be evaluated lazily
  • be composed

 

 

Thanks a lot to my Patreon Supporters: Meeting C++, Matt Braun, Roman Postanciuc, Venkata Ramesh Gudpati, Tobias Zindl, Marko, G Prvulovic, Reinhold Dröge, Abernitzke, Richard Ohnemus, Frank Grimm, Sakib, Broeserl, António Pina, Markus Falkner, Darshan Mody, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, and Animus24.

 

Thanks in particular to:   crp4

 

   

Get your e-book at Leanpub:

The C++ Standard Library

 

Concurrency With Modern C++

 

Get Both as one Bundle

cover   ConcurrencyCoverFrame   bundle
With C++11, C++14, and C++17 we got a lot of new C++ libraries. In addition, the existing ones are greatly improved. The key idea of my book is to give you the necessary information to the current C++ libraries in about 200 pages. I also included more than 120 source files.  

C++11 is the first C++ standard that deals with concurrency. The story goes on with C++17 and will continue with C++20.

I'll give you a detailed insight in the current and the upcoming concurrency in C++. This insight includes the theory and a lot of practice with more than 140 source files.

 

Get my books "The C++ Standard Library" (including C++17) and "Concurrency with Modern C++" in a bundle.

In sum, you get more than 700 pages full of modern C++ and more than 260 source files presenting concurrency in practice.

 

Get your interactive course

 

Modern C++ Concurrency in Practice

C++ Standard Library including C++14 & C++17

educative CLibrary

Based on my book "Concurrency with Modern C++" educative.io created an interactive course.

What's Inside?

  • 140 lessons
  • 110 code playgrounds => Runs in the browser
  • 78 code snippets
  • 55 illustrations

Based on my book "The C++ Standard Library" educative.io created an interactive course.

What's Inside?

  • 149 lessons
  • 111 code playgrounds => Runs in the browser
  • 164 code snippets
  • 25 illustrations

My Newest E-Books

Course: Modern C++ Concurrency in Practice

Course: C++ Standard Library including C++14 & C++17

Course: Embedded Programming with Modern C++

Course: Generic Programming (Templates)

Course: C++ Fundamentals for Professionals

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 1835

Yesterday 5295

Week 1835

Month 206882

All 4827776

Currently are 148 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments