templatesTypeTraits

The Type-Traits Library: Type Comparisons

In my last post, “The Type-Traits Library: Type Checks” I introduced type checks at compile time with the type-traits library. Today, I write about type comparisons at compile time.

 

templatesTypeTraits

The type-traits library empowers you to compare types at compile time. Compile time means that there are no costs involved at run time.

Comparing types

The type-traits library supports in C++11 three kinds of comparisons:

  • is_base_of<Base, Derived>
  • is_convertible<From, To>
  • is_same<T, U>

With C++20, we additionally got

  • is_pointer_interconvertible_with_class<From, To>
  • is_pointer_interconvertible_base_of<Base, Derived>

 

For simplicity reasons, I write only about the C++11 metafunctions.

Thanks to its member value, each class template returns true or false and is the optimal fit for static_assert.

 

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.cpp
    
    #include <cstdint>
    #include <iostream>
    #include <type_traits>
    
    class Base{};
    class Derived: public Base{};
    
    int main(){
      
      std::cout << std::boolalpha << '\n';
      
      std::cout << "std::is_base_of<Base,Derived>::value: " << std::is_base_of<Base,Derived>::value << '\n';
      std::cout << "std::is_base_of<Derived,Base>::value: " << std::is_base_of<Derived,Base>::value << '\n';
      std::cout << "std::is_base_of<Derived,Derived>::value: " << std::is_base_of<Derived,Derived>::value << '\n';
      
      // static_assert(std::is_base_of<Derived,Base>::value,"Derived is not base of Base");                  // (1) 
      
      std::cout << '\n';
      
      std::cout << "std::is_convertible<Base*,Derived*>::value: " << std::is_convertible<Base*,Derived*>::value << '\n';
      std::cout << "std::is_convertible<Derived*,Base*>::value: " << std::is_convertible<Derived*,Base*>::value << '\n';
      std::cout << "std::is_convertible<Derived*,Derived*>::value: " << std::is_convertible<Derived*,Derived*>::value << '\n';
      
      // static_assert(std::is_convertible<Base*,Derived*>::value,"Base* can not be converted to Derived*");  // (2)
      
      std::cout << '\n';
      
      std::cout << "std::is_same<int, int32_t>::value: " << std::is_same<int, int32_t>::value << '\n';
      std::cout << "std::is_same<int, int64_t>::value: " << std::is_same<int, int64_t>::value << '\n';
      std::cout << "std::is_same<long int, int64_t>::value: " << std::is_same<long int, int64_t>::value << '\n';
      
      // static_assert(std::is_same<int, int64_t>::value,"int is not the same type as int64_t");              // (3)
      
      std::cout << '\n';
      
    }

     

    The output of the program should not surprise you.

    compare

     

    If I use the static_assert in lines (1), (2), and (3), the assertion fires at compile time:

    compareStaticAssert

    The website cppreference.com contains possible implementations of all the metafunctions std::is_base_of,std::is_convertible,, and std::is_same. It’s pretty interesting and challenging to study them. 

    Possible Implementations

    First of all, here are possible implementations of the three metafunctions. Let me start with the simplest one, based on std::is_same.

    std::is_same

    I use in the following example the namespace rgr to distinguish my implementation from the C++ standard implementation.

     

    // isSame.cpp
    
    #include <iostream>
    #include <type_traits>
    
    namespace rgr {
    
      template<class T, T v>
      struct integral_constant {
          static constexpr T value = v;
          typedef T value_type;
          typedef integral_constant type;
          constexpr operator value_type() const noexcept { return value; }
          constexpr value_type operator()() const noexcept { return value; } //since c++14
      };
    
      typedef integral_constant<bool, true> true_type;                      // (2)              
      typedef integral_constant<bool, false> false_type;
      
      template<class T, class U>
      struct is_same : false_type {};                                       // (3)
     
      template<class T>
      struct is_same<T, T> : true_type {};
    
    }
    
    int main() {
    
        std::cout << '\n';
    
        std::cout << std::boolalpha;
    
        std::cout << "rgr::is_same<int, const int>::value: " 
                  << rgr::is_same<int, const int>::value << '\n';          // (1)
        std::cout << "rgr::is_same<int, volatile int>::value: " 
                  << rgr::is_same<int, volatile int>::value << '\n';
        std::cout << "rgr::is_same<int, int>::value: "  
                  << rgr::is_same<int, int>::value << '\n';
    
        std::cout << '\n';
    
        std::cout << "std::is_same<int, const int>::value: " 
                  << std::is_same<int, const int>::value << '\n';
        std::cout << "std::is_same<int, volatile int>::value: " 
                  << std::is_same<int, volatile int>::value << '\n';
        std::cout << "std::is_same<int, int>::value: "  
                  << std::is_same<int, int>::value << '\n';
    
        std::cout << '\n';
    
    }
    

     

     A short reminder: The invocation of the function template rgr::is_same<int, const int> (line 1) causes the invocation of the expression rgr::false_type::value (line 2), because std::is_same<> is derived from false_type (line 3). rgr::false_type::value is an alias for rgr::integral_constant<bool, false>::value (line 2). I use in the example the static constexpr value of the class integral_constant. integral_constant is the base class of the type-traits functions.

    Two facts are interesting when you study the following output. My implementations rgr::is_same behaves such as std::is_same, and const and volatile are part of the type.

    isSame

     It’s pretty straightforward to implement the metafunction isSameIgnoringConstVolatile based on the metafunction is_same.

     

    // isSameIgnoringConstVolatile.cpp
    
    #include <iostream>
    #include <type_traits>
    
    namespace rgr {
    
      template<class T, T v>
      struct integral_constant {
          static constexpr T value = v;
          typedef T value_type;
          typedef integral_constant type;
          constexpr operator value_type() const noexcept { return value; }
          constexpr value_type operator()() const noexcept { return value; } //since c++14
      };
    
      typedef integral_constant<bool, true> true_type;                       
      typedef integral_constant<bool, false> false_type;
    
      template<class T, class U>
      struct is_same : false_type {};
     
      template<class T>
      struct is_same<T, T> : true_type {};
      
      template<typename T, typename U>                                    // (1)
      struct isSameIgnoringConstVolatile: rgr::integral_constant<
             bool,
             rgr::is_same<typename std::remove_cv<T>::type, 
                          typename std::remove_cv<U>::type>::value  
         > {};
    
    }
    
    int main() {
    
        std::cout << '\n';
    
        std::cout << std::boolalpha;
    
        std::cout << "rgr::isSameIgnoringConstVolatile<int, const int>::value: " 
                  << rgr::isSameIgnoringConstVolatile<int, const int>::value << '\n';
        std::cout << "rgr::is_same<int, volatile int>::value: " 
                  << rgr::isSameIgnoringConstVolatile<int, volatile int>::value << '\n';
        std::cout << "rgr::isSameIgnoringConstVolatile<int, int>::value: "  
                  << rgr::isSameIgnoringConstVolatile<int, int>::value << '\n';
    
        std::cout << '\n';
    
    }
    

     

    The meta function isSameIgnoringConstVolatile derives from rgr::integral_constant and uses the function std::remove_cv to remove const or volatile from its types. As you may assume, std::remove_cv is a function from the type-traits library and allows you the modify types at compile time. I will write more about type modification in my next post.

    Finally, here is the output of the program:

    isSameIgnoringConstVolatile

    Let’s have a closer look at the two metafunctions std::is_base_of, and std::is_convertible. Here are the possible implementations from cppreference.com.

    std::is_base_of

    namespace details {
        template <typename B>
        std::true_type test_pre_ptr_convertible(const volatile B*);
        template <typename>
        std::false_type test_pre_ptr_convertible(const volatile void*);
     
        template <typename, typename>
        auto test_pre_is_base_of(...) -> std::true_type;
        template <typename B, typename D>
        auto test_pre_is_base_of(int) ->
            decltype(test_pre_ptr_convertible<B>(static_cast<D*>(nullptr)));
    }
     
    template <typename Base, typename Derived>
    struct is_base_of :
        std::integral_constant<
            bool,
            std::is_class<Base>::value && std::is_class<Derived>::value &&
            decltype(details::test_pre_is_base_of<Base, Derived>(0))::value
        > { };
    

    std::is_convertible

    namespace detail {
     
    template<class T>
    auto test_returnable(int) -> decltype(
        void(static_cast<T(*)()>(nullptr)), std::true_type{}
    );
    template<class>
    auto test_returnable(...) -> std::false_type;
     
    template<class From, class To>
    auto test_implicitly_convertible(int) -> decltype(
        void(std::declval<void(&)(To)>()(std::declval<From>())), std::true_type{}
    );
    template<class, class>
    auto test_implicitly_convertible(...) -> std::false_type;
     
    } // namespace detail
     
    template<class From, class To>
    struct is_convertible : std::integral_constant<bool,
        (decltype(detail::test_returnable<To>(0))::value &&
         decltype(detail::test_implicitly_convertible<From, To>(0))::value) ||
        (std::is_void<From>::value && std::is_void<To>::value)
    > {};
    

     

    Now, you know why I explained std::is_same. Here is my challenge.

    My Challenge

    Explain the previous implementation of the type-traits functions std::is_base_of and std::is_convertible. Send your explanation to Rainer.Grimm@ModernesCpp.de until Thursday (December 2th). The best answer for each function gets a coupon for my LeanPub bundle Modern C++ Collection.

    bundelI will publish the best answer to each function in my next post and name your first name. If I should name your full name, please write it.

    What’s next?

    Thanks to the type-traits library, you can modify types at compile time. I write about it in my next post.

     

    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)

    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 *