For more details, visit the Documentation.
Simple Template is a single-header, small, MSVC / GCC / Clang compatible, C++14 template metaprogramming library.
The development started as I needed more powerful metaprogramming tools than <type_traits>
in various projects. Boost::MPL felt too old-style as of C++14 era, and I was reluctant to pull in Boost dependencies anyway. On the other hand, MSVC was not ready for Boost::Hana due to its known lack of supports to several key language features. This library was heavily inspired by Boost::Hana's type constant approach and techniques from other template libraries.
- Types as constants:
tag<int> == tag<int>
- Type traits as constexpr functions:
tag<int>.category() == integral_tag
- Type transformation as constexpr operations:
tag<int&> - lvalue_reference_tag + const_qualifier_tag == tag<const int>
- Extensible type computation as constexpr functions taking and returning type consts
- Type branching as tag dispatching or tag specialization
The library provides easy to use wrappers and functions for two main use cases in template metaprogramming: type transformation and type branching. Fundamentally, every type-related action in generic programming falls into one of the two categories. Type transformations usually involve trait classes and nested types, and branching or dispatching mainly use template specialization, overloading and SFINAE. Writing generic code using these techniques are difficult and tedious work. Simple Template aims to improve these common use cases by providing cleaner and more natural syntax based on C++14 template consts.
Basically all compilers that are C++14 compliant. The development is driven by multi-platform unit testing to guarantee compatibility on all big-3:
- Visual C++: VS2015, recommended to install the latest VS2017
- GCC: > 5.0
- Clang: > 3.4
Include the single header, simpletemplate.hpp
and you are good to go.
Do note you need to explicitly use the integral constant operator in order to use numeric compile-time constants such as 0_c
:
using ST::operator""_c; //in your namespace
Tag<T>
and tag<T>
are the basic building blocks here. For better distinction, TitleCase symbols here represent types and snake_cases represent values, which can be variables, consts or functions. Tag<T>
is a wrapper type that contains type predicates and trait functions for T
, and tag<T>
is the only constexpr instance of the wrapper, that can be used as a value, passed around, or forcing template argument deduction.
From Tag<T>
, use Tag<T>::Type
for the actual wrapped type. From an instance of tag<T>
which can be an alias without mentioning T
, use decltype(tag)::Type
, this is useful when the type tag itself is the result of a more complex type deduction:
constexpr auto some_advanced_type_tag = <...>;//omitted
using MyType = decltype(some_advanced_type_tag)::Type;
Alternatively, it is possible to use TOTYPE(tag) macro for this.
Type tag of the same type T
are equal. Type tag of different types are unequal. Types are strictly compared, i.e. sensitive to const-volatile modifiers and references. The comparisons happens at compile time and can be tested in static_assert
s:
tag<int> == tag<int> //true
tag<T> == tag<int> //true if T is int
tag<const std::string&> == tag<std::string> //false
Now that we've converted types and their properties to tag values, type transformations can benefit from much better syntaxes with operators.
We can manipulate types just by +/-
modifier flags, like this:
tag<int> + lvalue_reference_tag == tag<int&> //true
tag<int&> - lvalue_reference_tag == tag<int> //true
tag<T> + const_qualifier_tag + lvalue_reference_tag == tag<const T&> //true
This may look redundant at first, since one can refer to const T&
when T
is already in the context, but it will come in handy when the type itself is generic and compound, like in this case:
template<class T>
void my_generic_func(T&& t)
{
constexpr auto T_noref = tag<T> - reference_tag;
my_generic_impl(std::forward<T>(t), T_noref.category(), T_noref.size);
}
All possible combinations are listed here: (still under development)
Type | Constant |
---|---|
ReferenceTag | reference_tag |
PointerTag | pointer_tag |
LValueReferenceTag | lvalue_reference_tag |
RValueReferenceTag | rvalue_reference_tag |
ConstQualifierTag | const_qualifier_tag |
Tag<T>::size()
is a constexpr function returning sizeof(T) as an integral constant.
static_assert(tag<int>.size() == 4_c, ""); //should be true most of the time :)
Tag<T>::category()
is a constexpr function that returns one of these type category tags:
Type | Constant |
---|---|
VoidTag | void_tag |
NullptrTag | nullptr_tag |
IntegralTag | integral_tag |
FloatingPointTag | floating_point_tag |
ArrayTag | array_tag |
EnumTag | enum_tag |
UnionTag | union_tag |
ClassTag | class_tag |
FunctionTag | function_tag |
PointerTag | pointer_tag |
LValueReferenceTag | lvalue_reference_tag |
RValueReferenceTag | rvalue_reference_tag |
PointerToMemberFunctionTag | pointer_to_member_object_tag |
PointerToMemberObjectTag | pointer_to_member_function_tag |
tag<int>.category() == integral_tag //true
tag<void>.category() == void_tag //true
tag<decltype(&C::f)>.category() == pointer_to_member_function_tag //true for member function f in class C
Alternatively, the library provides an equivalent free function type_category
.
Furthermore, a helper type alias TypeCategory<T>
refers to the same result but as a type:
TypeCategory<float> //FloatingPointTag, same as decltype(type_category<float>())
It is easily noticeable these also match up with type category predicates defined in the <type_traits>
header.
The advantage of making them types (and typed constants) rather than true/false, is that we can easily use tag-dispatching instead of type trait concatenation and SFINAE. This is shown in the following tag-dispatching example:
//catches integral types
template<typename T>
constexpr int generic_func_example_impl(T&& t, IntegralTag) { return 1; }
//catches enum types
template<typename T>
constexpr int generic_func_example_impl(T&& t, EnumTag) { return 2; }
//catches and discards everything else
constexpr int generic_func_example_impl(...) { return -1; }
template<typename T>
constexpr int generic_func_example(T&& t)
{
return generic_func_example_impl(t, type_category<T>());
}
static_assert(generic_func_example(32) == 1, "");
enum class E {A};
static_assert(generic_func_example(E::A) == 2, "");
static_assert(generic_func_example(3.f) == -1, "");
Another class specialization example where SFINAE is avoided:
template<typename T, typename = TypeCategory<T>>
struct GenericClassExample {
static constexpr int x = 0;
};
template<typename T>
struct GenericClassExample<T, EnumTag> {
static constexpr int x = 1;
};
template<typename T>
struct GenericClassExample<T, FloatingPointTag> {
static constexpr int x = 2;
};
template<typename T>
struct GenericClassExample<T, PointerTag> {
static constexpr int x = 3;
};
static_assert(GenericClassExample<int>::x == 0, "");
static_assert(GenericClassExample<double>::x == 2, "");
Tag-dispatching is generally cleaner and easier to use than SFINAE, especially when there is more than an instance / a set of trait to test, imagine:
template<class T, class U>
void generic_impl(T t, IntegralTag, U u, IntegralTag); //does something with integral op integral
template<class T, class U>
void generic_impl(T t, ClassTag, U u, IntegralTag); //does something with class.func(integral)
template<class T, class U>
void generic(T t, U u) { return generic_impl(t, type_category(t), u, type_category(u)); }
Such code would be very difficult and even ugly to write with SFINAE.
The library is under active development so API breaking changes can be expected.
- Type list
- More type traits
- Compile time type names (compiler dependent magic!)
- Documentation
- C++17 support (template auto integral constants)
Happy templating!