Concepts – Placeholders
C++11 has auto unconstrained placeholders. You can use concepts in C++20 as constrained placeholders. What seems at first glimpse not so thrilling is for me the decisive quantum leap. C++ templates will become an easy to use C++ feature.
Before I present the new syntax, I have to make a short remark. After my research to concepts and my experiments with unconstrained and constrained placeholders, I’m very biased. Therefore you can not expect a quite objective post.
An ever and ever-recurring question
I hear often in my C++ and Python seminars the question: When is a programming language easy? Of course, the answer can not be that a programming language is easy if you can solve difficult questions in an easy way. That is a contradiction.
For me, a programming language is easy if you can reduce it to a few simple principles. I call such a principle a red thread. I hope you get the German proverb. The idea of these few simple principles is that you can deduce the features of the language from these principles. According to my definition, Python is a simple programming language. For example, if you get the idea of building slices on a sequence, you can apply this principle in many contexts.
Therefore, the syntax will follow the same principle if I want to return each third element of a just-in-place created range range(0,10,3), a string, a list, or a tuple. The same principle will hold if I return the second element of a just-in-place created range range(9,0,-2), a string, a list or a tuple in reverse order.
According to my definition, C++98 is not a simple language. C++11 is something in between. For example, we have rules such as you can initialize all with curly braces (see { } – Initialization). Of course, even C++14 has a lot of features where I miss a simple principle. One of my favourites is the generalised lambda function.
1 2 3 4 5 6 |
auto genLambdaFunction= [](auto a, auto b) { return a < b; }; template <typename T, typename T2> auto genFunction(T a, T2 b){ return a < b; } |
By using the placeholder auto for the parameter a and b the generalised lambda function becomes in a magic way a function template. (I know, genLambdaFunction is a function object that has an overloaded call operator which accepts two type parameters.). genFunction is also a function template but you can not just define it by using auto. Hence you have to use a lot more syntax (line 3 and 4). That is the syntax which is often too difficult for a lot of C++ programmer.
Modernes C++ Mentoring
Do you want to stay informed: Subscribe.
Exactly that asymmetry will be removed with the placeholder syntax. Therefore, we have a new simple principle and C++ will become – according to my definition – a lot easier to use.
Placeholders
We will get unconstrained and constrained placeholders. auto is an unconstrained placeholder because a auto defined variable can be of any type. A concept is a constrained placeholder because it can only be used to define a variable that satisfies the concept. I introduced concepts in the post Concepts with the help of Haskell’s type classes. I got international praise and blame for my approach.
Let me define and use a simple concept before I dig into the details.
A simple concept
Thanks to the concept Integral, the arguments of my gcd algorithm have to be integrals.
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 |
// conceptsIntegral.cpp #include <type_traits> #include <iostream> template<typename T> concept bool Integral(){ return std::is_integral<T>::value; } template<typename T> requires Integral<T>() T gcd(T a, T b){ if( b == 0 ){ return a; } else{ return gcd(b, a % b); } } int main(){ std::cout << std::endl; std::cout << "gcd(100, 10)= " << gcd(100, 10) << std::endl; std::cout << "gcd(100, 33)= " << gcd(100, 33) << std::endl; // std::cout << "gcd(5.5, 4,5)= " << gcd(5.5, 4.5) << std::endl; std::cout << std::endl; } |
I define in line 6 the concept Integral. The concept Integral will evaluate to true if the predicate std::is_integral<T>::value returns true for T. std::is_integral<T> is a function of the type-traits library. The functions of the type-traits library enable amongst other things that you can check types at compile time. You can read the details about the type-traits in the posts about the type-traits library. In particular, I used the functions of the type-traits library to make the gcd algorithm more and more type-safe: More and More Save. I applied the concept in line 12. I will write in my next post how you can apply a concept in a simpler way. Therefore, the border between function templates and function successively distinguish.
But now, back to my small example. Thanks to the relatively new GCC 6.3 and the compiler flag -fconcepts, I can compile and run the program.
What will happen if I use line 26? The concept kicks in.
Once more, back to the placeholders. To be specific, constrained and unconstrained placeholders.
Constrained and unconstrained placeholders
You can use constrained placeholders (concepts) in each situation where you can use unconstrained placeholders (auto). If this is not an intuitive rule?
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 |
// conceptsPlaceholder.cpp #include <iostream> #include <type_traits> #include <vector> template<typename T> concept bool Integral(){ return std::is_integral<T>::value; } Integral getIntegral(auto val){ return val; } int main(){ std::cout << std::boolalpha << std::endl; std::vector<int> myVec{1, 2, 3, 4, 5}; for (Integral& i: myVec) std::cout << i << " "; std::cout << std::endl; Integral b= true; std::cout << b << std::endl; Integral integ= getIntegral(10); std::cout << integ << std::endl; auto integ1= getIntegral(10); std::cout << integ1 << std::endl; std::cout << std::endl; } |
For simplicity reasons, I reuse the concept Integral in line 7 – 10. Hence I iterate over integrals in the range-based for-loop in line 21 and my variable b in line 24 has to be integral. My usage of concepts goes on in line 27 and 30. I require in line 27 that the return type of getIntegral(10) has to fulfil the concept Integral. I’m not so strict in line 30. Here I’m fine with an unconstrained placeholder.
In the end, as ever, the output of the program. There was no surprise. Concepts behave totally intuitive.
That’s the end of my post. Of course, it’s not! I guess most of you didn’t recognise that I secretly introduced a new key feature of placeholders. Have a close look at the function getIntegral (line 12).
Integral getIntegral(auto val){ return val; }
The concept Integral as the return type is quite easy to get because it’s possible to use unconstrained placeholders as return type since C++11. With C++20, we can use – according to the simple rule – constrained placeholders. My point is a different one. I use auto for the type of the parameter. That is only possible for generalised lambda functions (see the first example). A generalised lambda function is under the hood a function template. Now, I will come back to my red thread. getIntegral becomes due to the auto parameter a function template. That is happening without the usual function template syntax. getIntegral accepts arbitrary types and returns only values of a type that fulfils the concept Integral.
What’s next?
In the next post, I will continue my story about placeholders because the unification of templates, concepts, and placeholders goes on.
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,and Matt Godbolt.
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!