The Type-Traits Library: Type Checks

The Type-Traits Library: Type Checks

This post is a cross-post from www.ModernesCpp.com.

The type-traits library is part of C++11 and supports type checks, type comparisons, and type modifications at compile-time. The library has more than 100 functions but grows with each new C++ standard release. Today, I present the type checks.

Type Checks

Each type belongs precisely to one of the fourteen primary type categories.

Primary Type Categories

Here are all of them:

template <class T> struct is_void;
template <class T> struct is_integral;
template <class T> struct is_floating_point;
template <class T> struct is_array;
template <class T> struct is_pointer;
template <class T> struct is_null_pointer;
template <class T> struct is_member_object_pointer;
template <class T> struct is_member_function_pointer;
template <class T> struct is_enum;
template <class T> struct is_union;
template <class T> struct is_class;
template <class T> struct is_function;
template <class T> struct is_lvalue_reference;
template <class T> struct is_rvalue_reference;        

The following program gives an example of a type fulfilling the check for each one of these primary type categories.

// primaryTypeCategories.cpp

#include <iostream>
#include <type_traits>

struct A {
  int a;
  int f(int) { return 2011; }
};

enum E {
  e= 1,
};

union U {
  int u;
};


int main() {

  using namespace std;
  
  cout <<  boolalpha <<  '\n';

  cout << is_void<void>::value << '\n';                               // true                           
  cout << is_integral<short>::value << '\n';                          // true
  cout << is_floating_point<double>::value << '\n';                   // true
  cout << is_array<int []>::value << '\n';                            // true
  cout << is_pointer<int*>::value << '\n';                            // true
  cout << is_null_pointer<nullptr_t>::value << '\n';                  // true
  cout << is_member_object_pointer<int A::*>::value <<  '\n';         // true
  cout << is_member_function_pointer<int (A::*)(int)>::value << '\n'; // true
  cout << is_enum<E>::value << '\n';                                  // true
  cout << is_union<U>::value << '\n';                                 // true 
  cout << is_class<string>::value << '\n';                            // true
  cout << is_function<int * (double)>::value << '\n';                 // true	
  cout << is_lvalue_reference<int&>::value << '\n';                   // true
  cout << is_rvalue_reference<int&&>::value << '\n';                  // true
  
}        

How Does this Magic Work?

This technique is based on templates and template specialization, a few conventions, and a lot of typing. I wrote a simplified version of the function template std::integral. std::integral check if a given type is an integral type. I ignore const or volatile qualifiers.

// integral.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>
  struct is_integral : public false_type{};

  template <>
  struct is_integral<bool> : public true_type{};

  template <>
  struct is_integral<char> : public true_type{};

  template <>
  struct is_integral<signed char> : public true_type{};

  template <>
  struct is_integral<unsigned char> : public true_type{};

  template <>
  struct is_integral<wchar_t> : public true_type{};

  template <>
  struct is_integral<short> : public true_type{};

  template <>
  struct is_integral<int> : public true_type{};                            // (3)

  template <>
  struct is_integral<long> : public true_type{};

  template <>
  struct is_integral<long long> : public true_type{};

  template <>
  struct is_integral<unsigned short> : public true_type{};

  template <>
  struct is_integral<unsigned int> : public true_type{};

  template <>
  struct is_integral<unsigned long> : public true_type{};

  template <>
  struct is_integral<unsigned long long> : public true_type{};
  
}

int main(){
  
  std::cout << std::boolalpha << '\n';
  
  std::cout << "std::is_integral<int>::value: " << std::is_integral<int>::value << '\n';
  std::cout << "rgr::is_integral<int>::value: " << rgr::is_integral<int>::value << '\n';    // (1)
  
  std::cout << "std::is_integral<double>::value: " << std::is_integral<double>::value << '\n';
  std::cout << "rgr::is_integral<double>::value: " << rgr::is_integral<double>::value << '\n';
  
  std::cout << '\n';
  
  std::cout << "std::true_type::value: " << std::true_type::value << '\n';
  std::cout << "rgr::true_type::value: " << rgr::true_type::value << '\n';
  
  std::cout << "std::false_type::value: " << std::false_type::value << '\n';
  std::cout << "rgr::false_type::value: " << rgr::false_type::value << '\n';
  
  std::cout << '\n';
  
  std::cout << "std::integral_constant<bool, true>::value: " << std::integral_constant<bool, true>::value << '\n';
  std::cout << "rgr::integral_constant<bool, true>::value: " << rgr::integral_constant<bool, true>::value << '\n';
  
  std::cout << "std::integral_constant<bool, false>::value: " << std::integral_constant<bool, false>::value << '\n';
  std::cout << "rgr::integral_constant<bool, false>::value: " << rgr::integral_constant<bool, false>::value << '\n';  
  
  std::cout << '\n';
  
}        

I use in my implementation the namespace rgr and compare it with type-traits functions in the namespace std. The invocation of the function template rgr::is_integral<int>::value (1) causes the invocation of the expression rgr::true_type::value (2), because integral<int> is derived from true_type (3). rgr::true_type::value is an alias for rgr::integral_constant<bool, true>::value (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.

For completeness, here is the output of the program. My implementation gives the same results, such as functions from the type-traits library.

No alt text provided for this image

I use in my function templates rgr::is_integral ::value as return. You maybe remember this convention from my previous post "Template Metaprogramming - How it works"? Right! My function templates rgr::is_integral are metafunctions and they use the naming conventions of template metaprogramming. Since C++17 there is for conventions a helper class for ::value. This helper class is based on variable templates.

template< class T >
inline constexpr bool is_integral_v = is_integral<T>::value        

Variable templates is essentially a family of variables. Due to this helper class, you can write std::integral_v<T> instead of std::integral<T>::value. This shortened notation works for all functions templates of the type traits library.

Composite type categories are then assembled from those primary type categories.

Composite Type Categories

There are seven composite type categories. The following table shows them.

No alt text provided for this image

Additionally, to the primary type categories and the composite type categories, the type traits library provides type properties and type property queries. For completeness, here are they.

Type Properties

template <class T> struct is_const;
template <class T> struct is_volatile;
template <class T> struct is_trivial;
template <class T> struct is_trivially_copyable;
template <class T> struct is_standard_layout;
template <class T> struct is_empty;
template <class T> struct is_polymorphic;
template <class T> struct is_abstract;
template <class T> struct is_final;
template <class T> struct is_aggregate;
 
template <class T> struct is_signed;
template <class T> struct is_unsigned;
template <class T> struct is_bounded_array;
template <class T> struct is_unbounded_array;
template <class T> struct is_scoped_enum;
 
template <class T, class... Args> struct is_constructible;
template <class T> struct is_default_constructible;
template <class T> struct is_copy_constructible;
template <class T> struct is_move_constructible;
 
template <class T, class U> struct is_assignable;
template <class T> struct is_copy_assignable;
template <class T> struct is_move_assignable;
 
template <class T, class U> struct is_swappable_with;
template <class T> struct is_swappable;
 
template <class T> struct is_destructible;
 
template <class T, class... Args> struct is_trivially_constructible;
template <class T> struct is_trivially_default_constructible;
template <class T> struct is_trivially_copy_constructible;
template <class T> struct is_trivially_move_constructible;
 
template <class T, class U> struct is_trivially_assignable;
template <class T> struct is_trivially_copy_assignable;
template <class T> struct is_trivially_move_assignable;
template <class T> struct is_trivially_destructible;
 
template <class T, class... Args> struct is_nothrow_constructible;
template <class T> struct is_nothrow_default_constructible;
template <class T> struct is_nothrow_copy_constructible;
template <class T> struct is_nothrow_move_constructible;
 
template <class T, class U> struct is_nothrow_assignable;
template <class T> struct is_nothrow_copy_assignable;
template <class T> struct is_nothrow_move_assignable;
 
template <class T, class U> struct is_nothrow_swappable_with;
template <class T> struct is_nothrow_swappable;
 
template <class T> struct is_nothrow_destructible;
 
template <class T> struct has_virtual_destructor;
 
template <class T> struct has_unique_object_representations;        

Many of the meta-functions like std::is_trivially_copyable have trivially in their name. That means that the compiler provides this method. Requesting a method from the compiler with the keyword default is also trivial.

Type Property Queries

template <class T> struct alignment_of;
template <class T> struct rank;
template <class T, unsigned I = 0> struct extent;        

What's next?

Did you notice the function std::is_same in the composite type category std::is_fundamental? std::is_same is special because it provides type comparisons at compile time? I will write about type comparison at compile time in my next post.

 

Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, Marko, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Animus24, Jozo Leko, John Breland, espkk, Louis St-Amour, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Neil Wang, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Tobi Heideman, Daniel Hufschläger, Red Trip, Alexander Schwarz, Tornike Porchxidze, Alessandro Pezzato, Evangelos Denaxas, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Dimitrov Tsvetomir, Leo Goodstadt, Eduardo Velasquez, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, and Michael Young.

Thanks in particular to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, and Rusty Fleming.

My special thanks to Embarcadero

 

Seminars

I'm happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.

Bookable (Online)

German

Standard Seminars (English/German)

Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.

New

Contact Me

Modernes C++


 

 

To view or add a comment, sign in

More articles by Rainer Grimm

  • Charity run for ALS

    Tomorrow, on the 28th, there will be a charity run for ALS in Rottenburg. The course is exactly 1 km long.

    2 Comments
  • Small Safety Improvements in the C++ 26 Core Language

    Safety is an important concern in C++26. Contracts are probably the most important feature for safety.

    1 Comment
  • My ALS Journey (30/n): Cippi at the CppCon

    This week was very exciting for Cippi. She visited CppCon in Aurora, near Denver.

    2 Comments
  • Contracts: Evaluation Semantic

    After briefly presenting the details of contracts in my last article, “Contracts: A Deep Dive“, I would like to take a…

  • My ALS Journey (29/n): I feel Good

    I often receive messages asking about my health and wishing me well. I am very happy to receive these messages and just…

    5 Comments
  • Contracts: A Deep Dive

    August 25, 2025/in C++26/by Rainer GrimmI already introduced contracts in the article “Contracts in C++26”. In this…

  • My ALS Journey (28/n): Bureaucracy – The German Disease

    Today I want to write about a sad topic. Bureaucracy in the German healthcare system is becoming increasingly absurd.

    2 Comments
  • Data-Parallel Types: Algorithms

    The data-parallel types library has four special algorithms for SIMD vectors. The four special algorithms are min, max,…

  • My ALS Journey (27/n): An Emergency Call

    Firstly, I would like to say that I am doing very well and have made a full recovery from my incident. However, I would…

    8 Comments
  • Data-Parallel Types: Reduction

    In this article, I will discuss reduction and mask reduction for data-parallel types. Reduction A reduction reduces the…

Others also viewed

Explore content categories