C++ Core Guidelines: Better Specific or Generic?
Concepts revolutionize the way we think about and use generic programming. They didn’t make it in C++11 or C++17 but with C++20, we will get them with a high probability.
Before I write about using concepts, I want to make a general remark.
Too Specific versus Too Generic
Until C++20 we have in C++ two diametral ways to think about functions or user-defined types (classes). Functions or classes can be defined on specific types or on generic types. In the second case, we call them function or class templates. What are the downsides of each way?
Too Specific
It’s quite a job to define a function or class for each specific type. To avoid that burden, type conversion often comes to our rescue but it is also part of the problem. Let’s see what I mean.
Narrowing Conversion
You have a function getInt(int a)
that you can invoke with a double
. Now, narrowing conversion takes place.
// narrowingConversion.cpp #include <iostream> void needInt(int i){ std::cout << "int: " << i << std::endl; } int main(){ std::cout << std::endl; double d{1.234}; std::cout << "double: " << d << std::endl; needInt(d); std::cout << std::endl; }
I assume that is not the behavior you wanted. You started with a double
and ended with an int
.
Modernes C++ Mentoring
Do you want to stay informed: Subscribe.
But conversion works also the other way around.
Integral Promotion
You have a user-defined type MyHouse
. An instance of MyHouse
can be constructed in two ways. When invoked without an argument (1), its attribute family
is set to an empty string. This means the house is still empty. To easily check if the house is empty or full, I implemented a conversion operator to bool
(2). Fine or? No!
// conversionOperator.cpp #include <iostream> #include <string> struct MyHouse{ MyHouse() = default; // (1) MyHouse(const std::string& fam): family(fam){} operator bool(){ return !family.empty(); } // (2) std::string family = ""; }; void needInt(int i){ std::cout << "int: " << i << std::endl; } int main(){ std::cout << std::boolalpha << std::endl; MyHouse firstHouse; if (!firstHouse){ std::cout << "The firstHouse is still empty." << std::endl; }; MyHouse secondHouse("grimm"); if (secondHouse){ std::cout << "Family grimm lives in secondHouse." << std::endl; } std::cout << std::endl; needInt(firstHouse); // (3) needInt(secondHouse); // (3) std::cout << std::endl; }
Now, instances of MyHouse
can be used when an int
is required. Strange!
Due to the overloaded operator bool (2), instances of MyHouse
can be used as an int
and can, therefore, be used in arithmetic expressions: auto res = MyHouse() + 5
. This was not my intention! Just for completeness. With C++11, you can declare conversion operators as explicit
. Therefore implicit conversions are not allowed.
My firm belief is that because of convenience reasons, we need the entire magic of conversions in C/C++ to deal with the fact that functions only accept specific arguments.
Are templates the cure? No!
Too Generic
Generic functions or classes can be invoked with arbitrary values. If the values do not satisfy the requirements of the function or class, no problem. You will get a compile-time error. Fine!
// gcd.cpp #include <iostream> template<typename 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) << std::endl; std::cout << gcd(3.5, 4.0)<< std::endl; std::cout << gcd("100", "10") << std::endl; std::cout << std::endl; }
What is the problem with this error message?
Of course, it is pretty long and quite challenging to understand, but my crucial concern is a different one. The compilation fails because neither double
or the C-strings supports the % operator. This means the error is due to the failed template instantiation for double
and C-string. This is too late and, therefore, really bad. No template instantiation for type double
or C-strings should be possible. The requirements for the arguments should be part of the function declaration and not a side-effect of an erroneous template instantiation.
Now concepts come to our rescue.
The Third Way
With concepts, we get something in between. We can define functions or classes that act on semantic categories with them. Meaning the arguments of functions or classes are either too specific or too generic but named sets of requirements such as Integral
.
What’s next?
Sorry for this short post, but one week before my multithreading workshop at the CppCon, I had neither the time nor the resources (no connectivity in the national parks in Washington state) to write an entire post. My next post will be particular because I will write about the CppCon. Afterward, I continue my story about generics and, in particular, about concepts.
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!