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.
I assume we all know what evolution or revolution means, but let me be more precise. These definitions are quite concise:
- Evolution is defined as gradual change, adaptation, progression, and metamorphosis.
- Revolution is the forcible overthrow of an entirely new system…drastic, disruptive, far-reaching, meaningful 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 about concepts. Consequentially, I was curious about what your opinion of 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 evolve. Honestly, I’m more on the revolution side.
Which argument speaks for evolution, and which argument speaks for revolution?
Evolution
Image by Alexas_Fotos from Pixabay
Modernes C++ Mentoring
Do you want to stay informed: Subscribe.
Clear Abstraction
When appropriately used, 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 abstract properties of the parameter classes are 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 essentially becomes 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 }
To make my point, I intentionally used a strange signature for my addConstrained function.
Revolution
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 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.
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); }
The 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 that 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 in 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 words 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 will present in the next post, is the first customer of concepts. Ranges support algorithms that can
- operate directly on the container
- be evaluated lazily
- be composed
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)
Rainer Grimm
Yalovastraße 20
72108 Rottenburg
Mail: schulung@ModernesCpp.de
Mentoring: www.ModernesCpp.org
Modernes C++ Mentoring,
Leave a Reply
Want to join the discussion?Feel free to contribute!