How to use typelists
The typelists are generic compile-time collections of types. If you use dynamic_cast, you are missing the point, because it should not be needed, because it is a static, compile time concept.
This works but I don't see the advantage over inheritance which is capable to do the same.
You cannot make any existing type inherit from anything you want. This is simply not feasible, because this existing type may be a built in type or a type from a library.
Think of the typelists as extensions of lists of types (e.g. in std::pair) for any reasonable number of types (instead of just 2).
The typelists can be used to create a facility to pass around a set of arguments to a function. This is a piece of code that calls generalized functors of 5 parameters (another concept from Modern C++ design) with the arguments supplied in a tupe (yet another one) with the typelist that defines types of objects held in the tuple:
//functor is just a holder of a pointer to method and a pointer to object to call this
//method on; (in case you are unfamiliar with a concept)
template<class R, class t0, class t1, class t2, class t3, class t4>
R call(Loki::Functor<R,LOKI_TYPELIST_5(t0, t1, t2, t3, t4
)> func,
Loki::Tuple<LOKI_TYPELIST_5(t0, t1, t2, t3, t4)> tuple)
{
///note how you access fields
return func(Loki::Field<0>(tuple), Loki::Field<1>(tuple),
Loki::Field<2>(tuple), Loki::Field<3>(tuple),
Loki::Field<4>(tuple));
}
//this uses the example code
#include<iostream>
using namespace std;
int foo(ostream* c,int h,float z, string s,int g)
{
(*c)<<h<<z<<s<<g<<endl;
return h+1
}
int main(int argc,char**argv)
{
Loki::Functor<int,LOKI_TYPELIST_5(ostream*, int, float, string, int)> f=foo;
//(...)
//pass functor f around
//(...)
//create a set of arguments
Loki::Tuple<LOKI_TYPELIST_5(ostream*, int, float, string, int)> tu;
Field<0>(tu)=&cout;
Field<1>(tu)=5;
Field<2>(tu)=0.9;
Field<3>(tu)=string("blahblah");
Field<4>(tu)=77;
//(...)
//pass tuple tu around, possibly save it in a data structure or make many
//specialized copies of it, or just create a memento of a call, such that
//you can make "undo" in your application; note that without the typelist
//you would need to create a struct type to store any set of arguments;
//(...)
//call functor f with the tuple tu
call(f,tu);
}
Note that only with other concepts like tuples or functors the typelists start to be useful.
Also, I have been experiencing Loki for about 2 years in a project and because of the template code (a lot of it) the sizes of executables in DEBUG versions tend to be BIG (my record was 35 MB or so). Also there was a bit of hit on the speed of compilation. Also recall that C++0x is probably going to include some equivalent mechanism. Conclusion: try not to use typelists if you don't have to.
Usage of typelist
Its also used in Mixin-Based Programming in C++ described by Ulrich W. Eisenecker, Frank Blinn, and Krzysztof Czarneck.
implementing a typelist in C++
With clang++
(Apple clang version 11.0.3 (clang-1103.0.32.59)) I get the following message:
t.cpp:51:8: error: class template partial specialization contains template parameters that cannot be deduced; this partial specialization will never be used [-Wunusable-partial-specialization]
struct Back<Typelist<Head...,Tail>>
^~~~~~~~~~~~~~~~~~~~~~~~~~~~
t.cpp:50:23: note: non-deducible template parameter 'Head'
template <typename... Head, typename Tail>
^
t.cpp:50:38: note: non-deducible template parameter 'Tail'
template <typename... Head, typename Tail>
^
From: https://en.cppreference.com/w/cpp/language/parameter_pack (sorry i don't have a primary ref)
In a primary class template, the template parameter pack must be the final parameter in the template parameter list.
In a function template, the template parameter pack may appear earlier in the list provided that all following parameters can be deduced from the function arguments, or have default arguments:
template<typename... Ts, typename U> struct Invalid; // Error: Ts.. not at the end
template<typename ...Ts, typename U, typename=void>
void valid(U, Ts...); // OK: can deduce U
// void valid(Ts..., U); // Can't be used: Ts... is a non-deduced context in this position
valid(1.0, 1, 2, 3); // OK: deduces U as double, Ts as {int,int,int}
But we can solve the issue by defining Back
in terms of Element
.
/**** get last element ****/
template <typename List>
struct Back;
template <typename... Args>
struct Back<Typelist<Args...>>
{
using type = typename Element<Typelist<Args...>, sizeof...(Args) - 1>::type;
};
Question:
Why do you have template using
statements for all your types except Back
and Element
?
Basic Typelist functionality
We first need some helper templates. The first one checks if two types are the same:
template <typename T, typename U>
struct is_same
{
// Default case: T and U are not the same type
static const bool value = false;
};
template <typename T>
struct is_same<T, T>
{
// Specialization: both template arguments are of the same type
static const bool value = true;
};
We can now use the boolean is_same<T, U>::value
to determine if the types T
and U
are equivalent.
Using is_same
we can now write a template which checks if a specific type occurs in a type list:
template <typename TList, typename T>
struct contains
{
static const bool value =
is_same<typename TList::head, T>::value // Base case
|| contains<typename TList::tail, T>::value; // Recursion
};
template <typename T>
struct contains<nulltype, T>
{
// Termination condition
static const bool value = false;
};
This is a recursive template, using a specialization on nulltype
to terminate the recursion.
One final helper template we need is enable_if
:
template <bool Cond, typename T=void>
struct enable_if
{
// Default case: Cond assumed to be false, no typedef
};
template <typename T>
struct enable_if<true, T>
{
// Specialization: Cond is true, so typedef
typedef T type;
};
enable_if<true, T>::type
yields T
, whereas enable_if<false, T>::type
is undefined. We exploit the SFINAE rule the enable or disable a function depending on its template argument, like so:
template <typename TList>
class OutputClass
{
public:
// Only enable the function if T is in TList (by SFINAE)
template <typename T>
typename enable_if<contains<TList, T>::value>::type
output(T t)
{
std::cout << t << std::endl;
}
};
Quite a trip, but if you understand all of this you're well on your way to master template metaprogramming. For an in-depth discussion of template metaprogramming I recommend you pick up a copy of C++ Template Metaprogramming (ISBN-13: 9780321227256).
Typelists visitor pattern example
What if you have 50 DocElement types? With the first example you need 50 if statements, with the second, you just need to have a typelist of 50 elements.
You can also think about what happens when you add another DocElement. In the first example, you need to go and change the if-else statements. With type-lists you can just add the new type to the end of the type-list.
The type-list code may seem like a lot of overhead, but you write it once, then you just use it, and instead of adding ifs or cases and code (which can get quite large over time) you just add types to the type-list. From a maintenance perspective the type-list code is far better than a huge switch statement or tens or hundreds of ifs.
As for improvements, I don't know, I'm still waiting for variadic templates and type aliases to be included in VS so that I can simplify the code even more.
Whenever I see a huge pile of repeating code, I start thinking about type-lists and metaprogramming, let the compiler do the work, not bored programmers. And the best part? You get zero penalty at runtime, it's just as efficient as the ifs (if you're careful with inlining)
Runtime typeswitch for typelists as a switch instead of a nested if's?
You'll need the preprocessor to generate a big switch
. You'll need get<>
to no-op out-of-bound lookups. Check the compiler output to be sure unused cases produce no output, if you care; adjust as necessary ;v) .
Check out the Boost Preprocessor Library if you care to get good at this sort of thing…
template <typename L>
struct type_switch
{
template< typename F >
void operator()( size_t i, F& f )
{
switch ( i ) {
#define CASE_N( N ) \
case (N): return f.operator()<typename impl::get<L,N>::type>();
CASE_N(0)
CASE_N(1)
CASE_N(2)
CASE_N(3) // ad nauseam.
}
};
implementing typelist replace operation using variadic templates
You might simplify your code and get rid of the recursion with
template <typename TList, typename T, typename U> struct ReplaceAll;
template <typename ... Ts, typename T, typename U>
struct ReplaceAll<Typelist<Ts...>, T, U>
{
using Result = Typelist<std::conditional_t<std::is_same_v<Ts, T>, U, Ts>...>;
};
Demo
From typelist to argument pack
This code converts typelist
to tuple
and calls foo
with simple types.
#include <tuple>
#include <iostream>
template<typename H, typename T>
struct typelist
{
typedef H Head;
typedef T Tail;
};
struct null_typelist {};
template<int... Indices>
struct indices {
using next = indices<Indices..., sizeof...(Indices)>;
};
template<int Size>
struct build_indices {
using type = typename build_indices<Size - 1>::type::next;
};
template<>
struct build_indices<0> {
using type = indices<>;
};
template<typename T>
using Bare = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
template<typename Tuple>
constexpr
typename build_indices<std::tuple_size<Bare<Tuple>>::value>::type
make_indices()
{ return {}; }
template<typename T, typename... Args>
struct tuple_push;
template<typename T, typename... Args>
struct tuple_push<T, std::tuple<Args...>>
{
typedef std::tuple<Args..., T> type;
};
template<typename TL>
struct typelist_to_tuple;
template<typename H, typename T>
struct typelist_to_tuple<typelist<H, T>>
{
typedef typename tuple_push<H, typename typelist_to_tuple<T>::type>::type type;
};
template<typename H>
struct typelist_to_tuple<typelist<H, null_typelist>>
{
typedef std::tuple<H> type;
};
struct Float {
float get() const { return 7.5; }
};
struct Int {
int get() const { return 7; }
};
template<typename... Args>
void foo(const Args&... args)
{
}
template<typename T, typename... Args>
void foo(const T& current, const Args&... args)
{
std::cout << current << std::endl;
foo(args...);
}
template<typename Tuple, int... Indices>
void apply(const Tuple& tuple, indices<Indices...>)
{
foo(std::get<Indices>(tuple).get()...);
}
template<typename Tuple>
void apply(const Tuple& tuple)
{
apply(tuple, make_indices<Tuple>());
}
int main()
{
typedef typelist<Int, typelist<Float, typelist<Int, null_typelist>>> list;
typedef typelist_to_tuple<list>::type tuple;
tuple t = std::make_tuple(Int(), Float(), Int());
apply(t);
}
live example
How to get an element of type list by index
I want to avoid using tuples as type lists for use cases, that require
instantiation for passing a list likef(L{})
If you don't want to instanciate std::tuple
but you're ok with it in
unevaluated contexts, you may take advantage of std::tuple_element
to
implement your typeAt
trait:
template <std::size_t I, typename T>
struct typeAt;
template <std::size_t I, typename... Args>
struct typeAt<I, type_list<Args...>> : std::tuple_element<I, std::tuple<Args...>> {};
// ^ let library authors do the work for you
using L = type_list<int, char, float, double>;
using T = typename typeAt<2, L>::type;
static_assert(std::is_same<T, float>::value, "");
Related Topics
Are Data Members Allocated in the Same Memory Space as Their Objects in C++
How to Call a Cmake Function from Add_Custom_Target/Command
Factory Method Implementation - C++
Any Good Solutions for C++ String Code Point and Code Unit
What's the Standard/Official Name for Universal References
The Cxx Compiler Identification Is Unknown
C++: Calling Member Function via Pointer
Is There Any Automated Way to Implement Post-Constructor and Pre-Destructor Virtual Method Calls
Three Forward Slashes for Block Commenting
Any Good Reason Why Assignment Operator Isn't a Sequence Point
Injected Class Name Compiler Discrepancy
Sse2 Intrinsics - Comparing Unsigned Integers
Specifying Default Parameter When Calling C++ Function
What Does the & (Ampersand) at the End of Member Function Signature Mean