C++ Traits and Meta-programming


Creative Commons License
This -C++ Traits and Meta-programming- tutorial is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License
Headlines
Meta-programming

Rule(s)

Example Palindrome.Cpp.zip 

// 'constexpr' works with 'static' only...
// 'constexpr' allows compile-time initialization:
constexpr static const std::pair<char, char> _Intervals[] = {std::pair<char, char>('a', 'z'), std::pair<char, char>('A', 'Z')};
constexpr static const int _Intervals_size = 2; // No need with C++17: 'std::size(_Intervals)'

Example Constexpr.Cpp.zip 

template<int... Integers> // C++14
constexpr std::array<int, sizeof...(Integers)> create_array(std::integer_sequence<int, Integers...>) noexcept {
	return std::array<int, sizeof...(Integers)> { Integers... }; // Aggregate initialization...
}
constexpr std::array<int, 20> My_array = ::create_array(std::make_integer_sequence<int, 20>{});
static_assert(My_array[19] == 19);

Rule(s)

Example Constexpr.Cpp.zip 

class Constexpr_example {
public:
    constexpr static int N = 8;
};

template<int N> constexpr bool Is_prime() noexcept {
	if constexpr (N < 2) return false; // C++17
	if constexpr (N == 2) return true; // C++17
	else { // This branch is discarded at compilation time when 'N == 2'
		bool is_prime = true;
		for (int i = 2; i < N; i++)
			if (N % i == 0) {
				is_prime = false;
				break;
			}
		return is_prime;
	}
}

static_assert(::Is_prime<2>()); // No compilation error
// static_assert(::Is_prime<Constexpr_example::N>()); // Compilation error since 'Constexpr_example::N == 8'

Rule(s)

Example (naive approach) Constexpr_.Cpp.zip 

constexpr int N = 10; // Must be initialized
constexpr int Native_array[N] = {}; // Must be initialized

constexpr int Primes_() {
    int cursor = 0;
    for (int i = 2; cursor != N; i++) { // Candidate prime numbers...
        // Naive solution:
        bool is_prime = true;
        for (int j = 2; j < i; j++)
            if (i % j == 0) {
                is_prime = false;
                break;
            }
        //        if (is_prime)
        //            // Compilation error: assignment of read-only location:
        //            Native_array[cursor++] = i;
    }
	return 0; // Done...
}

 int Return = Primes_(); // This code causes the compiler to crash in a random way!
 // static_assert(::Native_array[0] == 2); // It fails!

constexpr std::array<int, N> Primes() {
    std::array<int, N> primes = {}; // Must be initialized
    int cursor = 0;
    for (int i = 2; cursor != N; i++) { // Candidate prime numbers...
        // Naive solution:
        bool is_prime = true;
        for (int j = 2; j < i; j++)
            if (i % j == 0) {
                is_prime = false;
                break;
            }
        //        if (is_prime)
        //            // Compilation error: call to non-'constexpr' function:
        //            primes[cursor++] = i;
        return primes;
    }
}

class Constexpr_example {
public:
    constexpr static std::array<int, N> Primes = ::Primes();
};

Example (compilation-time code only!) Constexpr.Cpp.zip 

constexpr int Not_prime = -1;

constexpr int As_prime(int n) {
	if (n < 2) return ::Not_prime;
	for (int i = 2; i < n; i++)
		if (n % i == 0)
			return ::Not_prime;
	return n;
}

// 'std::index_sequence' and 'std::make_index_sequence' are specializations of
// 'std::integer_sequence' and 'std::make_integer_sequence' with 'T' = 'std::size_t'
/** Internal '_create_array' template function called by 'create_array' */
template<typename Function, std::size_t... Integers>
// Trailing return type thanks to 'auto':
constexpr/*[]*/ auto _create_array(Function f, std::index_sequence<Integers...>) ->
// Returned type:
std::array<std::invoke_result_t<Function, std::size_t>, sizeof...(Integers)> {
	return { { f(Integers)... } }; // Aggregate initialization
}

template<int N, typename Function>
// Trailing return type based on 'auto':
constexpr/*[]*/ auto create_array(Function f) ->
// Returned type:
std::array<typename std::result_of<Function(std::size_t)>::type, N> {
	// 'std::make_index_sequence<7>{}' -> '<0,1,2,3,4,5,6,7>' become 'Integers'
	return _create_array<Function>(f, std::make_index_sequence<N>{});
}

constexpr std::array<int, Constexpr_example::N> Primes =
	::create_array<Constexpr_example::N/*, decltype(&::As_prime)*/>(::As_prime);
static_assert(Primes[0] == ::Not_prime); // No compilation error
static_assert(Primes[1] == ::Not_prime); // No compilation error
static_assert(Primes[2] != ::Not_prime); // No compilation error
static_assert(Primes[3] != ::Not_prime); // No compilation error

Rule(s)

Example Constexpr.Cpp.zip 

int main() {
    …
    /** Code is put inside 'main' for illustration purpose only! */
    // 'N' is made available in local scope ('main') for *CAPTURE*;
    constexpr int N = Constexpr_example::N;
    // 'constexpr' lambda expression:
    constexpr auto my_lambda = [N /* Capture 'constexpr' only */]() {
        if (::Is_prime<Constexpr_example::N>()) // '::Is_prime<N>()' *DOES NOT* work...
            return ::As_prime(N); // '::As_prime(Constexpr_example::N)' *DOES* work...
        return ::Not_prime;
    };
    static_assert(my_lambda() == ::Not_prime); // No compilation error since 'Constexpr_example::N == 8'
}

See also

Basic traits: std::is_class, std::is_same

Rule(s)

Example (std::is_same) SFINAE.Cpp.zip 

std::cout << std::boolalpha << std::is_same<std::pair<const std::string, std::string>,
    std::unordered_map<std::string, std::string >::value_type >::value << std::endl; // 'true'

Example SFINAE.Cpp.zip 

'.h' file:
class ClassWith_toString {
    public:
        std::string toString() {return "Returned by 'toString'...";}
    …
};
class ClassWithout_toString final {};

'.cpp' file:
std::cout << std::boolalpha;
std::cout << std::is_class<ClassWith_toString>::value << std::endl; // 'true'
std::cout << std::is_trivially_copyable<ClassWith_toString>::value << std::endl; // 'true'
std::cout << std::is_final<ClassWith_toString>::value << std::endl; // 'false'
std::cout << std::is_final<ClassWithout_toString>::value << std::endl; // 'true'

Reinforcing type checking

Rule(s)

Example Meta_programming.Cpp.zip 

// '.h' file:
#include <type_traits>
class Note {
    virtual void initialization();
};
class Confidential_note : public Note {
    void initialization() override;
};

template <typename T> // 'my_function' has to work with a polymorphic type...
// Return type is 'void' due to 'std::enable_if_t' inference succeeds:
typename std::enable_if_t<std::is_polymorphic_v<T> > my_function(T&&); // C++17 required

// '.cpp' file:
int main() {
    ::my_function(Note());
    ::my_function(Confidential_note());
    // ::my_function("'std::string' is not a polymorphic type..."); // Compilation error...
}

Code adaptation

Rule(s)

Example Inheritance_genericity_gcc.Cpp.zip 

template<typename Any> class Pointer_based_hash {
public:
    // If 'std::is_pointer<Any>::value' is true then 'std::enable_if' has a public member 'typedef' type, which equals 'Return_type' else there is no member 'typedef' 
    template<typename Return_type = std::size_t> typename std::enable_if<std::is_pointer<Any>::value, Return_type>::type
    operator()(const Any pointer) const {
        std::hash < std::bitset<sizeof (Any)> > hash;
        return hash(reinterpret_cast<uintptr_t> (pointer)); // Storage relies on memory address!
    }
    template<typename Return_type = std::size_t> typename std::enable_if<!std::is_pointer<Any>::value, Return_type>::type
    operator()(const Any& any) const {
        std::hash < std::bitset<sizeof (Any*)> > hash;
        return hash(reinterpret_cast<uintptr_t> (&any)); // Storage relies on memory address!
    }
};

template <typename T> class Garden {
private:
    // 'Pointer_based_hash<T>' (as second anonymous type) acts as a functor (definition of 'operator()' required in 'Pointer_based_hash<T>'):
    std::unordered_set <T, Pointer_based_hash<T> > _garden;
public:
    Garden(const T&);
};

Substitution Failure Is Not An Error (SFINAE)

Rule(s)

Example (simple) Meta_programming.Cpp.zip 

// '.h' file:
#include <type_traits>class A {
public:
    // virtual ~A(); // 'A' *IS NOT* polymorphic!
    using Internal_type = A; // For simplification, but stupid...
};

template <typename T> // 'my_function' has to work with a polymorphic type...
// Return type is 'void' due to 'std::enable_if_t' inference succeeds:
typename std::enable_if_t<std::is_polymorphic_v<T> > my_function(T&&) { std::cout << "Base ver." << std::endl; }; // C++17 required
// Specialization of 'my_function':
template <typename T> void my_function(typename T::Internal_type&&) { std::cout << "Specialization, SFINAE" << std::endl; };

// '.cpp' file:
int main() {
    ::my_function<A>(A()); // 'Specialization, SFINAE'
}

Example (complex) SFINAE.Cpp.zip 

// '.h' file:
template <typename T> class Has_toString { // Inspiration: https ://gist.github.com/fenbf/d2cd670704b82e2ce7fd
private:
	typedef wchar_t No; // 'sizeof(wchar_t) == 2'
	static_assert(sizeof(No) == 2, "'sizeof(No) != 2', impossible!");
	typedef char Yes; // 'sizeof(char) == 1'
	static_assert(sizeof(Yes) == 1, "'sizeof(Yes) != 1', impossible!");
	// Two following functions have *NO BODY* since they only play a role at *compile time* (uncomment for test)
	// Versions are instantiated by the compiler according to *effective type* injected in 'Has_toString':
	template <typename ...> static No _Test(...) /*{ std::string s = "Never executed..."; }*/;
	/* '_Test(...)' is inferred matches to all parameters. It is then inferred when 'Yes _Test(decltype(&C::toString))' *IS NOT*
	*/
	template <typename C> static Yes _Test(decltype(&C::toString)); // Get the (returned) type of the 'toString' function via member function pointer!
	/* '_Test(decltype(&C::toString))' is inferred since 'std::declval<std::nullptr_t>())' conforms to 'decltype(&C::toString)'
	*/
public:
	static std::string Get_metadata() {
		std::string result = "decltype(std::declval<Yes>()): " + std::string(typeid(decltype(std::declval<Yes>())).name()) + '\n'
			+ "decltype(_Test<T>(nullptr)): " + std::string(typeid(decltype(_Test<T>(nullptr))).name());
		return result;
	};
	// 'T' plays the role of 'C' in 'Test', i.e., 'std::string' plays the role of 'T' in 'std::enable_if<Has_toString<T>::Result, std::string>'
	static constexpr const bool Result = sizeof(_Test<T>(std::declval<std::nullptr_t>())) == sizeof(Yes);
	// Simplified alternative ('nullptr' matches to the member function pointer '&C::toString'):
	static constexpr const bool Result_ = std::is_same<decltype(_Test<T>(nullptr)), 
		// 'std::declval<Yes>()' returns a *rvalue ref.* whose type is 'Yes':
		std::remove_reference_t<decltype(std::declval<Yes>())> >::value; // Remove '&&'
};

template<typename T>
// 'Has_toString<T>::Result' exprime une condition calculée "à la compilation"
// La fonction 'Call_toString' *n'est pas instanciable* du point de vue de la généricité si 'false' :
/* C++14: */ typename std::enable_if_t<Has_toString<T>::Result, std::string> // <=> 'std::enable_if<Has_toString<T>::Result, std::string>::type' in C++11
Call_toString(T* t) {
    /* 'T' has 'toString' ... */
    return t->toString();
}
std::string Call_toString(...) { // Version disponible (*non instanciée*) pour traiter 'false'
	return "'toString()' method is undefined...";
}

// '.cpp' file:
class ClassWith_toString {
    public:
        std::string toString() {return "Returned by 'toString'...";}
        …
};

class ClassWithout_toString final {};
…
std::cout << std::boolalpha << "Has_toString<ClassWith_toString>: " << Has_toString<ClassWith_toString>::Result_ << std::endl; // 'true'
std::cout << std::boolalpha << "Has_toString<ClassWithout_toString>: " << Has_toString<ClassWithout_toString>::Result << std::endl; // 'false'
std::cout << std::boolalpha << "Has_toString<int>: " << Has_toString<int>::Result << std::endl; // 'false'

“My effective type is itself a template class!”

Rule(s)

Example Trait.Cpp.zip 

// '.h' file:
template<typename T> class My_template { /* Given a template class...*/ };

namespace V1 {
    template<typename Realized, template<typename> class Realizer> class is_realization_of : public std::integral_constant<bool, false > /* <=> std::false_type */ {};
    template<typename Realized, template<typename> class Realizer> class is_realization_of<Realizer<Realized>, Realizer> : public std::true_type {};
    // 'My_template_realization' is indeed 'My_template<T>' for some 'T':
    template<typename My_template_realization> class My_class {
        // Caution: comma is misinterpreted by the pre-processor, requiring extra parentheses:
        static_assert((is_realization_of<My_template_realization, My_template>::value), "failure");
    };
}

namespace V2 { // For any number of (template) effective types...
    template<template<typename...> class TT, typename T> class is_realizer_of : std::false_type {};
    template<template<typename...> class TT, typename... Ts> class is_realizer_of<TT, TT<Ts...> > : std::true_type {};
    template<typename My_template_realization> class My_class {
        static_assert((is_realizer_of<My_template, My_template_realization>::value), "failure");
    };
}

// '.cpp' file:
// V1::My_class<int> mc1; // 'static_assert' error since 'int' is not a template class!
V1::My_class<My_template<int> > mc2; // OK!

See also