Compare and Modify Types
The type-traits library empowers you to compare and modify types. All is done at compile time therefore, there is no performance penalty.
Comparing types
The type-traits library supports three kinds of comparisons:
- is_base_of<Base, Derived>
- is_convertible<From, To>
- is_same<T, U>
Thanks to its member value, each class template returns true or false and is the optimal fit for static_assert.
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 |
// compare.cpp #include <cstdint> #include <iostream> #include <type_traits> class Base{}; class Derived: public Base{}; int main(){ std::cout << std::boolalpha << std::endl; std::cout << "std::is_base_of<Base,Derived>::value: " << std::is_base_of<Base,Derived>::value << std::endl; std::cout << "std::is_base_of<Derived,Base>::value: " << std::is_base_of<Derived,Base>::value << std::endl; std::cout << "std::is_base_of<Derived,Derived>::value: " << std::is_base_of<Derived,Derived>::value << std::endl; // static_assert(std::is_base_of<Derived,Base>::value,"Derived is not base of Base"); std::cout << std::endl; std::cout << "std::is_convertible<Base*,Derived*>::value: " << std::is_convertible<Base*,Derived*>::value << std::endl; std::cout << "std::is_convertible<Derived*,Base*>::value: " << std::is_convertible<Derived*,Base*>::value << std::endl; std::cout << "std::is_convertible<Derived*,Derived*>::value: " << std::is_convertible<Derived*,Derived*>::value << std::endl; // static_assert(std::is_convertible<Base*,Derived*>::value,"Base* can not be converted to Derived*"); std::cout << std::endl; std::cout << "std::is_same<int, int32_t>::value: " << std::is_same<int, int32_t>::value << std::endl; std::cout << "std::is_same<int, int64_t>::value: " << std::is_same<int, int64_t>::value << std::endl; std::cout << "std::is_same<long int, int64_t>::value: " << std::is_same<long int, int64_t>::value << std::endl; // static_assert(std::is_same<int, int64_t>::value,"int is not the same type as int64_t"); std::cout << std::endl; } |
The output of the program should not surprise you.
Modernes C++ Mentoring
Do you want to stay informed: Subscribe.
If I use the static_assert in lines 18, 26, and 34, the assertion will fire at compile time.
Modifying types
Now I’m a little bit pedantic. However, the C++ standard speaks about the modification or transformation of types that are not accurate. At compile time, there is no state. Therefore, there is nothing to modify. You can only generate new types on request. The type-traits library is template metaprogramming in a very beautiful robe. Template metaprogramming is a purely functional language that is embedded in C++. Purely functional languages have no state. I said that I will continue to speak about modifying types in the rest of this post.
The type-traits library has many functions to modify types at compile time. Therefore, you can remove or add const or volatile properties from a type. But there is more: Remove the sign of a type or the dimension of an array; change the pointer or reference properties of y type.
Here is the overview:
// const-volatile modifications template <class T> struct remove_const; template <class T> struct remove_volatile; template <class T> struct remove_cv; template <class T> struct add_const; template <class T> struct add_volatile; template <class T> struct add_cv; // reference modifications template <class T> struct remove_reference; template <class T> struct add_lvalue_reference; template <class T> struct add_rvalue_reference; // sign modifications template <class T> struct make_signed; template <class T> struct make_unsigned; // array modifications template <class T> struct remove_extent; template <class T> struct remove_all_extents; // pointer modifications template <class T> struct remove_pointer; template <class T> struct add_pointer;
To get from a reference int& at compile time the type int, you have to use the member type of the class template. In C++14, this becomes a lot easier. You have only to add _t to the function. That holds for all invocated functions of this section.
The key of the code snippet is that you can write with C++14 std::remove_reference<int &>:: type simply in the form std::remove_reference_t<int &>. Thanks to value, you get the result of the comparison std::is_same
For completeness, I will mention that I should write about the modifications std::conditional, std::common_type, and std::enable_if. But I don’t want to repeat myself. I presented the three functions in the post Statically checked. To get the rest of the details, read the Miscellaneous section transformations on the page cppreference.com.
One question is still open.
How does the whole magic work?
Due to a little bit of template metaprogramming, I can easily implement the class templates is_same and remove_const. I use the namespace rgr to distinguish my implementation from the C++ implementation.
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 40 41 42 43 44 45 |
// removeConst.cpp #include <iostream> #include <string> #include <type_traits> namespace rgr{ template<class T, class U> struct is_same : std::false_type {}; template<class T> struct is_same<T, T> : std::true_type {}; template< class T > struct remove_const{ typedef T type; }; template< class T > struct remove_const<const T> { typedef T type; }; } int main(){ std::cout << std::boolalpha << std::endl; std::cout << std::is_same<int,std::remove_const<const int>::type>::value << std::endl; std::cout << rgr::is_same<int,rgr::remove_const<const int>::type>::value << std::endl; typedef rgr::remove_const<double>::type myDouble; std::cout << rgr::is_same<double,myDouble>::value << std::endl; typedef rgr::remove_const<const std::string>::type myString; std::cout << rgr::is_same<std::string,myString>::value << std::endl; typedef rgr::remove_const<std::add_const<int>::type>::type myInt; std::cout << rgr::is_same<int,myInt>::value << std::endl; std::cout << std::endl; } |
I implemented is_same and remove_const in the namespace rgr. This corresponds to the type-traits library. For simplicity reasons, I use the static constants std::false_type and std::true_type (lines 10 and 13). I presented them in the post Check types. Thanks to the base class std::false_type, the class template has a member value, respectively, for std::true_type. The critical observation of the class template is_same is to distinguish the general template (lines 9 and 10) from the partially specialized template (lines 12 and 13. The compiler will use the partially specialized template if both template arguments have the same type. The partially specialized template has, in opposite to the general template, only one type parameter. My reasoning for the class template remove_const is similar. The general template returns via its member type precisely the same type; the partially specialized template returns the new type after removing the const property (line 22). The compiler will choose the partially specialized template if its template argument is const.
The rest is quickly explained. I use in lines 31 and 32 the functions of the type-traits library and my versions. I declare in line 34 a typedef mydouble, a type myString (line 37), and a type myInt. All types are non-constant.
Here is the output of the program.
What’s next?
I intentionally ignored the import capability of the type-traits library. In the first step, the compiler can analyze your types at compile time and perform powerful optimizations in the second step. The result is you have a faster program. Due to the type-traits library, this is happening in various standard template library algorithms. As far as I know, all implementations of the STL use this technique. I will write in a further post about this automatic optimization. But in the next post, I present user-defined literals. This is my favorite feature in modern C++ if you write safety-critical software. User-defined literals empower you to calculate with units. The compiler takes care that you don’t compare apples and pears.
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!