Introduction
This tip is all about simple, but useful tricks with the template metaprogramming.
I used this trick to generate a versatile and optimal code.
Background
C++11 introduced a new <type_traits> (also here) library, which became a faithful assistant to me and many other C++ programmers, so if you've still missed it for any reason - it is a good time to take a closer look.
Using the Code
Code parts from the header module ("TypeHelpers.h"):
#include <cstddef>
#include <cstdint>
#include <type_traits>
1) BoolType
template <const bool Bool>
struct BoolType {};
template<>
struct BoolType<true> {
typedef std::true_type Type;
};
template<>
struct BoolType<false> {
typedef std::false_type Type;
};
#define BOOL_TYPE(BoolVal) BoolType<BoolVal>::Type
This struct
is a simple converter from a bool value to the bool type (for reversed conversion the std::true_type
and std::false_type
itself can be used, as they have an operator bool defined)
2) AddRemoveConst
template <typename T, const bool Constant>
struct AddRemoveConst {};
template <typename T>
struct AddRemoveConst<T, true> {
typedef const T Type;
};
template <typename T>
struct AddRemoveConst<T, false> {
typedef T Type;
};
#define ADD_REMOVE_CONST(Type, StaticPredicate) AddRemoveConst<Type, StaticPredicate>::Type
This struct
is pretty self-explainable. Through std::add_const
/ std::remove_const
can be used to achieve the same logic, I need it both in a one single class.
3) TypeTag
enum class ECFundamentalTypeTags {
UNIDENTIFIED,
BOOL,
SIGNED_CHAR,
UNSIGNED_CHAR,
WCHAR,
SIGNED_SHORT_INT,
UNSIGNED_SHORT_INT,
SIGNED_INT,
UNSIGNED_INT,
SIGNED_LONG_INT,
UNSIGNED_LONG_INT,
SIGNED_LONG_LONG_INT, UNSIGNED_LONG_LONG_INT, FLOAT,
DOUBLE,
LONG_DOUBLE,
VOID_,
NULLPTR };
template <typename T, class TypeTags = ECFundamentalTypeTags>
struct TypeTag {
static const auto TAG = TypeTags::UNIDENTIFIED;
};
template <class TypeTags>
struct TypeTag<bool, TypeTags> {
static const auto TAG = TypeTags::BOOL;
};
template <class TypeTags>
struct TypeTag<signed char, TypeTags> {
static const auto TAG = TypeTags::SIGNED_CHAR;
};
template <class TypeTags>
struct TypeTag<unsigned char, TypeTags> {
static const auto TAG = TypeTags::UNSIGNED_CHAR;
};
template <class TypeTags>
struct TypeTag<wchar_t, TypeTags> {
static const auto TAG = TypeTags::WCHAR;
};
template <class TypeTags>
struct TypeTag<signed short int, TypeTags> {
static const auto TAG = TypeTags::SIGNED_SHORT_INT;
};
template <class TypeTags>
struct TypeTag<unsigned short int, TypeTags> {
static const auto TAG = TypeTags::UNSIGNED_SHORT_INT;
};
template <class TypeTags>
struct TypeTag<signed int, TypeTags> {
static const auto TAG = TypeTags::SIGNED_INT;
};
template <class TypeTags>
struct TypeTag<unsigned int, TypeTags> {
static const auto TAG = TypeTags::UNSIGNED_INT;
};
template <class TypeTags>
struct TypeTag<signed long int, TypeTags> {
static const auto TAG = TypeTags::SIGNED_LONG_INT;
};
template <class TypeTags>
struct TypeTag<unsigned long int, TypeTags> {
static const auto TAG = TypeTags::UNSIGNED_LONG_INT;
};
template <class TypeTags>
struct TypeTag<signed long long int, TypeTags> {
static const auto TAG = TypeTags::SIGNED_LONG_LONG_INT;
};
template <class TypeTags>
struct TypeTag<unsigned long long int, TypeTags> {
static const auto TAG = TypeTags::UNSIGNED_LONG_LONG_INT;
};
template <class TypeTags>
struct TypeTag<float, TypeTags> {
static const auto TAG = TypeTags::FLOAT;
};
template <class TypeTags>
struct TypeTag<double, TypeTags> {
static const auto TAG = TypeTags::DOUBLE;
};
template <class TypeTags>
struct TypeTag<long double, TypeTags> {
static const auto TAG = TypeTags::LONG_DOUBLE;
};
template <class TypeTags>
struct TypeTag<void, TypeTags> {
static const auto TAG = TypeTags::VOID;
};
template <class TypeTags>
struct TypeTag<std::nullptr_t, TypeTags> {
static const auto TAG = TypeTags::NULLPTR;
};
#define TYPE_TAG(Object) TypeTag<std::decay<decltype(Object)>::type>::TAG
This type helper allows to do switch
by type. Through both std::type_info::hash_code and std::type_index can be used to do type-to-integral-number mapping, they are not compile time constants, so cannot be used with the switch
statement (but can be used with if
, while
, STL containers, etc.). Although, you can try to use C++11 constexpr, but my MS VS 2013 Community Update 5 does not support it (sadly).
As you can see, TypeTag
can use any TypeTags
and is easily expandable to support any user type.
4) IntegralTypeBySize
template <const size_t Size, const bool Signed>
struct IntegralTypeBySize {
static const auto TAG = ECFundamentalTypeTags::UNIDENTIFIED;
};
template<>
struct IntegralTypeBySize<1U, true> {
typedef int8_t Type;
static const auto TAG = TypeTag<Type, ECFundamentalTypeTags>::TAG;
};
template<>
struct IntegralTypeBySize<2U, true> {
typedef int16_t Type;
static const auto TAG = TypeTag<Type, ECFundamentalTypeTags>::TAG;
};
template<>
struct IntegralTypeBySize<4U, true> {
typedef int32_t Type;
static const auto TAG = TypeTag<Type, ECFundamentalTypeTags>::TAG;
};
template<>
struct IntegralTypeBySize<8U, true> {
typedef int64_t Type;
static const auto TAG = TypeTag<Type, ECFundamentalTypeTags>::TAG;
};
template<>
struct IntegralTypeBySize<1U, false> {
typedef uint8_t Type;
static const auto TAG = TypeTag<Type, ECFundamentalTypeTags>::TAG;
};
template<>
struct IntegralTypeBySize<2U, false> {
typedef uint16_t Type;
static const auto TAG = TypeTag<Type, ECFundamentalTypeTags>::TAG;
};
template<>
struct IntegralTypeBySize<4U, false> {
typedef uint32_t Type;
static const auto TAG = TypeTag<Type, ECFundamentalTypeTags>::TAG;
};
template<>
struct IntegralTypeBySize<8U, false> {
typedef uint64_t Type;
static const auto TAG = TypeTag<Type, ECFundamentalTypeTags>::TAG;
};
In template algorithms, I used this helper class to provide a memory chunk of an exact size and MSB (Most Significant Bit) handling. It based on C++11 introduced fixed width integer types.
Test code for Ideone online compiler:
#include <iostream>
template<typename T>
void f(T obj) throw() {
switch(TYPE_TAG(obj)) {
case ECFundamentalTypeTags::SIGNED_INT:
case ECFundamentalTypeTags::UNSIGNED_INT:
std::cout << "int!" << std::endl;
break;
case ECFundamentalTypeTags::SIGNED_LONG_LONG_INT:
case ECFundamentalTypeTags::UNSIGNED_LONG_LONG_INT:
std::cout << "long long int!" << std::endl;
break;
case ECFundamentalTypeTags::FLOAT:
case ECFundamentalTypeTags::DOUBLE:
case ECFundamentalTypeTags::LONG_DOUBLE:
std::cout << "floating point number!" << std::endl;
break;
default:
std::cout << "unknown!" << std::endl;
}
}
#include <cassert>
#include <typeinfo>
int main() {
const BOOL_TYPE(true) btt;
static_assert(btt(), "");
std::cout << btt() << std::endl;
const BOOL_TYPE(false) btf;
static_assert(!btf(), "");
std::cout << btf() << std::endl;
auto v_1_ = false;
static_assert(ECFundamentalTypeTags::BOOL == TYPE_TAG(v_1_), "");
auto v_2_ = 0;
static_assert(ECFundamentalTypeTags::SIGNED_INT == TYPE_TAG(v_2_), "");
auto v_3_ = 0L;
static_assert(ECFundamentalTypeTags::SIGNED_LONG_INT == TYPE_TAG(v_3_), "");
auto v_4_ = 0ULL;
static_assert(ECFundamentalTypeTags::UNSIGNED_LONG_LONG_INT == TYPE_TAG(v_4_), "");
f(1), f(1ULL), f(1.0), f(1.0L);
IntegralTypeBySize<sizeof(char), true>::Type t1_ = 0;
static_assert(sizeof(t1_) == sizeof(char), "");
static_assert(std::is_integral<decltype(t1_)>::value &&
std::is_signed<decltype(t1_)>::value, "");
IntegralTypeBySize<sizeof(int), true>::Type t2_ = 0;
static_assert(sizeof(t2_) == sizeof(int), "");
static_assert(std::is_integral<decltype(t2_)>::value &&
std::is_signed<decltype(t2_)>::value, "");
IntegralTypeBySize<sizeof(long long int), true>::Type t4_ = 0;
static_assert(sizeof(t4_) == sizeof(long long int), "");
static_assert(std::is_integral<decltype(t4_)>::value &&
std::is_signed<decltype(t4_)>::value, "");
const IntegralTypeBySize<8U, false>::Type array[32U] = {0};
std::cout << "\ni've got a " <<
sizeof(array) / sizeof(*array) << " length array of "
<< typeid(*array).name() << "s here!\n";
return 0;
}
Output:
1
0
int!
long long int!
floating point number!
floating point number!
i've got a 32 length array of ys here!
Notes:
AddRemoveConst
is removed as it is not compiled by Ideone (ok with MS VS 2013 Community Update 5)
TYPE_TAG
macro is changed from:
#define TYPE_TAG(Object) TypeTag<std::decay<decltype(Object)>::type>::TAG
to:
#define TYPE_TAG(Object) TypeTag<decltype(Object)>::TAG
for the same reason.
Points of Interest
This module is just a small part of the library, which uses C++11 features and which I am working under now, I decided to make it a public
property.
History
- Update 1: Minor code fix, now OK with the Ideone online compiler