Compare and Modify Types

Contents[Show]

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 support 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 therefore 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.

 compare

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

 compareStaticAssert

 

Modifying types

Now I'm a little bit pedantic. Although the C++ standard speaks about the modification or transformation of types that is not totally 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. Said that I will continue to speak about the modification of types in the rest of this post.

The type-traits library has quite a bunch of functions to modify types at compile time. Therefore, you can remove const or volatile properties from a type or add it. 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;
    

In order 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 the 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 section Miscellaneous transformations at the page cppreference.com.

 One questions is still open.

How does the whole magic work?

Due to a little bit of template metaprogramming, I can implement the class templates is_same and remove_const quite easily. I use the namespace rgr to distinguish my implementation from for 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 reason I use the static constants std::false_type and std::true_type (line 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 key observation of the class template is_same is to distinguish the general template (line 9 and 10) from the partially specialized template (line 12 and 13. The compiler will use the partially specialized template if the 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 exactly the same type; the partially specialized template returns the new type after removing the const property (line22). The compiler will choose the partially specialized template, if its template argument is const. 

The rest is quickly explained. I use in the lines 31 and 32 the functions of the type-traits library and my own versions. I declare in line 34 a typedef mydouble, a type myString (lne 37), and a type myInt. All types are non-constant.

Here is the output of the program.

 

 removeConst

What's next?

I intentionally ignored an import capability of the type-traits library. The compiler can in the first step analyse your types at compile time and perform in the second step powerful optimizations. The result is you have a faster program. Due to the type-traits library this is actually happening in various algorithms of the standard template library. As far as I know all implementations of the STL uses this technique. I will write in a further post about this automatic optimization. But in the next post I present user-defined literals. My favourite feature in modern C++ if you write safety critical software. User-defined literals empowers you to calculate with units. The compiler takes care that you don't compare apples and pears.

 

 

 

 

 

 

title page smalltitle page small Go to Leanpub/cpplibrary "What every professional C++ programmer should know about the C++ standard library".   Get your e-book. Support my blog.

 

Comments   

0 #1 Maran 2016-10-23 12:16
The revealed magic is fascinating. :) BTW, I think Base and Derived are misplaced in
is_base_of
where you list the kinds of comparisons.
Quote
0 #2 Rainer Grimm 2016-10-23 19:30
Quoting Maran:
The revealed magic is fascinating. :) BTW, I think Base and Derived are misplaced in
is_base_of
where you list the kinds of comparisons.

Thanks. I fixed it.
Quote

Add comment


My Newest E-Books

Latest comments

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 329

All 543224

Currently are 221 guests and no members online