compare

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.

Rainer D 6 P2 500x500

 

Rainer D 6 P2 500x500Modernes C++ Mentoring

  • "Fundamentals for C++ Professionals" (open)
  • "Design Patterns and Architectural Patterns with C++" (open)
  • "C++20: Get the Details" (open)
  • "Concurrency with Modern C++" (open)
  • "Generic Programming (Templates) with C++": October 2024
  • "Embedded Programming with Modern C++": October 2024
  • "Clean Code: Best Practices for Modern C++": March 2025
  • Do you want to stay informed: Subscribe.

     

     compare

    If I use the static_assert in lines 18, 26, and 34, the assertion will fire at compile time.

     compareStaticAssert

     

    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.

    std::cout << std::is_same<int,std::remove_reference<int &>::type>::value << std::endl; // true
    std::cout << std::is_same<int,std::remove_reference_t<int &>>::value << std::endl; // true

     

    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.

     

     removeConst

    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)

    Do you want to stay informed about my mentoring programs? Subscribe Here

    Rainer Grimm
    Yalovastraße 20
    72108 Rottenburg

    Mobil: +49 176 5506 5086
    Mail: schulung@ModernesCpp.de
    Mentoring: www.ModernesCpp.org

    Modernes C++ Mentoring,

     

     

    0 replies

    Leave a Reply

    Want to join the discussion?
    Feel free to contribute!

    Leave a Reply

    Your email address will not be published. Required fields are marked *