C++: Can a macro expand "abc" into 'a', 'b', 'c'? - c++

I've written a variadic template that accepts a variable number of char parameters, i.e.
template <char... Chars>
struct Foo;
I was just wondering if there were any macro tricks that would allow me to instantiate this with syntax similar to the following:
Foo<"abc">
or
Foo<SOME_MACRO("abc")>
or
Foo<SOME_MACRO(abc)>
etc.
Basically, anything that stops you from having to write the characters individually, like so
Foo<'a', 'b', 'c'>
This isn't a big issue for me as it's just for a toy program, but I thought I'd ask anyway.

I've created one today, and tested on GCC4.6.0.
#include <iostream>
#define E(L,I) \
(I < sizeof(L)) ? L[I] : 0
#define STR(X, L) \
typename Expand<X, \
cstring<E(L,0),E(L,1),E(L,2),E(L,3),E(L,4), E(L,5), \
E(L,6),E(L,7),E(L,8),E(L,9),E(L,10), E(L,11), \
E(L,12),E(L,13),E(L,14),E(L,15),E(L,16), E(L,17)> \
cstring<>, sizeof L-1>::type
#define CSTR(L) STR(cstring, L)
template<char ...C> struct cstring { };
template<template<char...> class P, typename S, typename R, int N>
struct Expand;
template<template<char...> class P, char S1, char ...S, char ...R, int N>
struct Expand<P, cstring<S1, S...>, cstring<R...>, N> :
Expand<P, cstring<S...>, cstring<R..., S1>, N-1>{ };
template<template<char...> class P, char S1, char ...S, char ...R>
struct Expand<P, cstring<S1, S...>, cstring<R...>, 0> {
typedef P<R...> type;
};
Some test
template<char ...S>
struct Test {
static void print() {
char x[] = { S... };
std::cout << sizeof...(S) << std::endl;
std::cout << x << std::endl;
}
};
template<char ...C>
void process(cstring<C...>) {
/* process C, possibly at compile time */
}
int main() {
typedef STR(Test, "Hello folks") type;
type::print();
process(CSTR("Hi guys")());
}
So while you don't get a 'a', 'b', 'c', you still get compile time strings.

A solution based on Sylvain Defresne's response above is possible in C++11:
#include <boost/preprocessor/repetition/repeat.hpp>
#include <boost/preprocessor/punctuation/comma_if.hpp>
template <unsigned int N>
constexpr char get_ch (char const (&s) [N], unsigned int i)
{
return i >= N ? '\0' : s[i];
}
#define STRING_TO_CHARS_EXTRACT(z, n, data) \
BOOST_PP_COMMA_IF(n) get_ch(data, n)
#define STRING_TO_CHARS(STRLEN, STR) \
BOOST_PP_REPEAT(STRLEN, STRING_TO_CHARS_EXTRACT, STR)
// Foo <STRING_TO_CHARS(3, "abc")>
// expands to
// Foo <'a', 'b', 'c'>
Further, provided the template in question is able to handle multiple terminating '\0' characters, we may ease the length requirement in favor of a maximum length:
#define STRING_TO_CHARS_ANY(STR) \
STRING_TO_CHARS(100, STR)
// Foo <STRING_TO_CHARS_ANY("abc")>
// expands to
// Foo <'a', 'b', 'c', '\0', '\0', ...>
The above examples compile properly on clang++ (3.2) and g++ (4.8.0).

There has been a lot of trials, but it is ultimately doomed to fail I think.
To understand why, one needs to understand how the preprocessor works. The input of the preprocessor can be thought of as a stream. This stream is first transformed in preprocessing-tokens (list availabe in The C++ Programming Language, 3rd Edition, Annexe A Grammar, page 795)
On these tokens, the preprocessor may only apply a very restricted number of operations, apart from the digrams/trigrams stuff, this amount to:
file inclusion (for header directives), this may not appear in a macro as far as I know
macro substitution (which is extremely complicated stuff :p)
#: transforms a token into a string-literal token (by surrounding it by quotes)
##: concatenates two tokens
And that's it.
There is no preprocessor instruction that may split a token into several tokens: this is macro substitution, which means actually having a macro defined in the first place
There is no preprocessor instruction to transform a string-literal into a regular token (removing the quotes) that could then be subject to macro substitution.
I therefore hold the claim that it is impossible (either in C++03 or C++0x), though there might (possibly) be compiler specific extensions for this.

Based on user1653543's solution above.
Some template magic:
template <unsigned int N>
constexpr char getch (char const (&s) [N], unsigned int i)
{
return i >= N ? '\0' : s[i];
}
template<char ... Cs>
struct split_helper;
template<char C, char ... Cs>
struct split_helper<C, Cs...>
{
typedef push_front_t<typename split_helper<Cs...>::type, char_<C>> type;
};
template<char ... Cs>
struct split_helper<'\0', Cs...>
{
typedef std::integer_sequence<char> type;
};
template<char ... Cs>
using split_helper_t = typename split_helper<Cs...>::type;
Some PP magic:
#define SPLIT_CHARS_EXTRACT(z, n, data) \
BOOST_PP_COMMA_IF(n) getch(data, n)
#define STRING_N(n, str) \
split_helper_t<BOOST_PP_REPEAT(n, SPLIT_CHARS_EXTRACT, str)>
#define STRING(str) STRING_N(BOOST_PP_LIMIT_REPEAT, str)
split_helper just helper to cut trailing zeroes. Now STRING("Hello") is a typed compile-time char sequence (std::integer_sequence<char, 'H', 'e', 'l', 'l', 'o'>). Length of string constants is up to BOOST_PP_LIMIT_REPEAT characters.
Homework: implement push_front_t and c_str to get null-terminated string of std::integer_sequence<char, ...>. (Although, you can try to use Boost.MPL)

this used to work in an early version of msvc, I don't know if it still does:
#define CHAR_SPLIT(...) ##__VA_ARGS__

Unfortunately, I believe this cannot be done. The best you can get from the preprocessor is provided by Boost.Preprocessor, most notably through its data types :
array : syntax would be (3, (a, b, c))
list : syntax would be (a, (b, (c, BOOST_PP_NIL)))
sequence : syntax would be (a)(b)(c)
tuple : syntax would be (a, b, c)
From any of these types, you can easily create a macro which would build a comma separated list of single-quote enclosed items (see for example BOOST_PP_SEQ_ENUM), but I believe the input of this macro will have to be one of these types, and all require the characters to be typed individually.

Based on what I was discussing above, the following awful template hackery may be sufficient to pull this off. I haven't tested this (sorry!), but I'm pretty sure it or something close to it might work.
The first step is to build a template class that just holds a tuple of chars:
template <char... Chars> class CharTuple {};
Now, let's build an adapter that can transform a C-style string into a CharTuple. To do this, we'll need the following helper class which is essentially a LISP-style cons for tuples:
template <typename Tuple, char ch> class Cons;
template <char... Chars, char ch> class Cons<CharTuple<Chars... ch>> {
typedef CharTuple<ch, Chars...> type;
}
Let's also assume we have a meta-if statement:
template <bool Condition, typename TrueType, typename FalseType> class If {
typedef typename TrueType::type type;
};
template <typename TrueType, typename FalseType> class If<False> {
typedef typename FalseType::type type;
};
Then the following should let you convert a C-style string into a tuple:
template <typename T> class Identity {
typedef T type;
};
template <char* str> class StringToChars {
typedef typename If<*str == '\0', Identity<CharTuple<>>,
Cons<*str, typename StringToChars<str + 1>::type>>::type type;
};
Now that you can convert a C-style string into a tuple of chars, you can funnel your input string through this type to recover the tuple. We'll need to do a bit more machinery to get this working, though. Isn't TMP fun? :-)
The first step is to take your original code:
template <char... Chars> class Foo { /* ... */ };
and use some template specialization to convert it to
template <typename> class FooImpl;
tempalte <char... Chars> class FooImpl<CharTuple<Chars...>> { /* ... */ };
It's just another layer of indirection; nothing more.
Finally, you should be able to do this:
template <char* str> class Foo {
typedef typename FooImpl<typename StringToChars<str>::type>::type type;
};
I really hope this works. If it doesn't, I still think this is worth posting because it's probably ε-close to a valid answer. :-)

In C++14, this can be done by using an immediately invoked lambda and a static member function, similar to BOOST_HANA_STRING:
#include <utility>
template <char... Cs>
struct my_string {};
template <typename T, std::size_t... Is>
constexpr auto as_chars_impl(std::index_sequence<Is...>) {
return my_string<T::str()[Is]...>{};
}
template <typename T>
constexpr auto as_chars() {
return as_chars_impl<T>(
std::make_index_sequence<sizeof(T::str())-1>{});
}
#define STR(literal) \
[]{ \
struct literal_to_chars { \
static constexpr decltype(auto) str() { \
return literal; \
} \
}; \
return as_chars<literal_to_chars>(); \
}()
Live on Godbolt
Before C++17, the object returned by STR("some literal") can't be constexpr because the lambda can't be constexpr.
Before C++20, you can't just write decltype(STR("some literal")) because lambdas are not allowed in unevaluated contexts.

Related

string literals as template arguments forces code duplication and verbosity

A compile-time created string is being used as a character array in a structure and its max size is driven by that string. Also that structure is being used as a member variable in another struct, and there are multiple of such string buffer structs in it, which in turn drives its storage size.
Using strings as template arguments does work, but introduces multiple code repetitions, spreading like wild fire through the source code. Matters get even worse when the first structure has multiple strings as template arguments (2 or even 3).
Here's a working example:
#include <cstddef>
static constinit decltype(auto) c = "abc";
//static constinit decltype(auto) c2 = "12";
// size of A is driven by compile-time-created strings
template<typename T, size_t N /*, typename T2, size_t N2*/>
struct A{
char str[N] {};
// char str2[N2] {};
consteval A(const T(&s)[N] /*, const T2(&s2)[N2]*/) {
for(int i = 0; i < N; ++i){
str[i] = s[i];
if(s[i] == 0) // for strings shorter than storage
break;
}
// for(int i = 0; i < N2; ++i){
// str2[i] = s2[i];
// if(s2[i] == 0)
// break;
// }
}
};
// dummy function which's sole purpose is to help with the type deduction
template<typename T, size_t N /*, typename T2, size_t N2*/>
consteval auto f(const T(&s)[N] /*, const T2(&s2)[N2]*/){
return A<T, N /*, T2, N2*/>(s /*, s2*/);
}
// size of B's members and struct's total size are driven by compile-time
// created strings
struct B{
// explicit (manual) error-prone template params
A<char, 4 /*, char, 3*/> xxx{c /*, c2*/};
// use of a dummy function feels very wrong
decltype(f(c /*, c2*/)) yyy{c /*, c2*/};
// also uses dummy function
decltype(f("abc" /*, "12"*/)) zzz{"abc" /*, "12"*/};
// would like to be able to use shorter strings
//A<char, 4 /*, char, 3*/> fail{"a" /*, "1"*/};
};
The 3 working examples of usage are either prone to mistakes or introduce too much code repetition. And the last one, that is not working, is a nice-to-have sort of thing along the lines of "I wonder if this is doable?" to me.
Link to Compiler Explorer
Is there anything I'm missing in the C++ standard that would let me avoid repetition while retaining this automation to avoid potential manual mistakes?
Ideally I'd've loved to just code like this snippet below:
struct B{
// automatic type deduction without some dummy function's help
A xxx{c /*, c2*/};
// strings in multiple TUs produce independent templates in current standard :(
A<"abc" /*, c2*/> xxx;
};
// usage
// constructs with { {c /*, c2*/}, {"abc" /*, c2*/} }
constinit B b;
// constructs with { {c /*, c2*/}, {"bc" /*, "0"*/} }
constinit B b2{{}, {"bc" /*, "0"*/}};
Without using standard library headers (excepting <cstddef>), here's a definition for the class template A that gets most of the desired features with as little boilerplate as possible, given that C++ disallows automatic argument deduction in non-static struct member declarations. This uses a c++17 user-defined deduction guide:
#include <cstddef>
template <typename T, std::size_t N>
struct A {
// + 1 to account for null termination in storage
T str[N + 1];
template <std::size_t... Ns>
// to prevent aggregate construction from compiling
// if sum of string literal sizes exceed storage capacity
requires(N + 1 > (0 + ... + (Ns - 1)))
consteval A(const T (&...s)[Ns]) {
auto it = str;
(..., [&](const auto &r) {
for (const auto c : r) {
if (!c) break;
*it++ = c;
}
}(s));
*it = T{};
}
};
// user-defined deduction guide
template <typename T, std::size_t... Ns>
// - 1 to exclude null termination from each string literal in the pack
A(const T (&...s)[Ns]) -> A<T, (0 + ... + (Ns - 1))>;
Usage and tests below, with obligatory Compiler Explorer link:
static constinit decltype(auto) c1 = "abc";
static constinit decltype(auto) c2 = "12";
struct B {
// no dummy function
// but requires repetition of the compile-time constants
// in the type expression
decltype(A{c1, c2}) value{c1, c2};
};
#include <concepts>
// deduces correct width from compile-time constants
static_assert(std::same_as<decltype(B{}.value), A<char, 5>>);
#include <string_view>
using namespace std::string_view_literals;
// default construction
static_assert(B{}.value.str == "abc12"sv);
// aggregate construction
static_assert(B{{}}.value.str == ""sv);
static_assert(B{{c1}}.value.str == "abc"sv);
static_assert(B{{c2}}.value.str == "12"sv);
static_assert(B{{c2, c1}}.value.str == "12abc"sv);
static_assert(B{{"a", "1"}}.value.str == "a1"sv);
// candidate template ignored: constraints not satisfied [with Ns = <3, 5>]
// because '5UL + 1 > 0 + (3UL - 1) + (5UL - 1)' (6 > 6) evaluated to false
// static_assert(B{{"ab", "1234"}}.value.str == "ab1234"sv);
To completely eliminate duplication in the declaration, you can create a derived class that behaves like A{c1, c2}. To make the linkage well-behaved, you'll need to canonicalize the template arguments of the derived class. Here's one way to do that:
template <typename T, T... Cs>
struct C : decltype(A{{Cs..., T{}}}) {
using base = decltype(A{{Cs..., T{}}});
using base::base;
constexpr C() : base{{Cs..., T{}}} {}
};
#include <utility>
template <A, typename...>
struct to_c;
template <typename T, std::size_t N, A<T, N> a>
struct to_c<a> : to_c<a, std::make_index_sequence<N>> {};
template <typename T, std::size_t N, A<T, N> a, std::size_t... Is>
struct to_c<a, std::index_sequence<Is...>> {
using type = C<T, a.str[Is]...>;
};
template <A... a>
using to_c_t = typename to_c<A{a.str...}>::type;
static constinit decltype(auto) c1 = "abc";
static constinit decltype(auto) c2 = "12";
#include <concepts>
static_assert(std::same_as<to_c_t<c1, c2>, C<char, 'a', 'b', 'c', '1', '2'>>);
For your purposes, to_c_t<c1, c2> value; should behave almost like decltype(A{c1, c2}) value{c1, c2};, except there's no duplication, and it is safe to use across translation units since it is an alias for the canonical type C<char, 'a', 'b', 'c', '1', '2'>. Just for completeness, here's the full example using this approach.

Turn C string literal into std::integer_sequence at compile-time with C++17

Is it possible to write a construct that turns any C string literal "XY..." into a type std::integer_sequence<char, 'X', 'Y', ...> at compile time? I really want a character pack, not a std::array.
Something like
using MyStringType = magic_const_expr("MyString");
A nice approach using the literal operator was demonstrated here but unfortunately it requires a non-standard compiler extension.
Something like this might do it:
template <size_t N, typename F, size_t... indexes>
constexpr auto make_seq_helper(F f, std::index_sequence<indexes...> is) {
return std::integer_sequence<char, (f()[indexes])...>{};
}
template <typename F>
constexpr auto make_seq(F f) {
constexpr size_t N = f().size();
using indexes = std::make_index_sequence<N>;
return make_seq_helper<N>(f, indexes{});
};
template<const char* str>
struct IntegerSequenceFromString {
private:
constexpr static auto value = make_seq([](){return std::string_view{str}; });
public:
using type = decltype(value);
};
Usage would then be:
constexpr static const char str[] = "lala";
IntegerSequenceFromString<str>::type i{};
Here is a live example of it working.
I am sure, there is a way to reduce some of this extra stuff as well, but from the assembly, it looks like we don't generate any real runtime variables: https://godbolt.org/z/f65cjGfzn

checking uniqueness of characters at compile time

Is it possible in C++11 (not later) to write a function that verifies the uniqueness of characters passed to it at compile time
verify('a');
verify('b');
verify('c');
verify('a'); //should cause compilation error
[Edit by MK to answer some questions]:
The calls are always in the same scope, right one after the other like above.
A macro solution would be acceptable too
Non-type template parameters are acceptable too
Not exactly what you asked for, but given your constraints (same scope and macro solution is acceptable) you can try something like this:
#define verify(x) class _tmp_##x {};
Example:
int main()
{
verify(a);
verify(b);
verify(a);
return 0;
}
Will fail compilation due to redefinition of local class _tmp_a.
template<std::size_t X>
struct line_t { enum{value=X}; constexpr line_t(){} };
template<std::size_t line, char c>
constexpr std::integral_constant<bool, false> use_flag(
std::integral_constant<char,c>, line_t<line>
) { return {}; }
#define FLAG_USE( C ) \
constexpr std::integral_constant<bool, true> use_flag( \
std::integral_constant<char,C>, line_t<__LINE__> \
) { return {}; }
template<char c, std::size_t line>
constexpr std::size_t count_uses( line_t<line> from, line_t<1> length ){
return use_flag( std::integral_constant<char, c>{}, from )();
}
template<char c, std::size_t line>
constexpr std::size_t count_uses( line_t<line> from, line_t<0> length ){
return 0;
}
template<char c, std::size_t f, std::size_t l>
constexpr std::size_t count_uses(line_t<f> from, line_t<l> length ){
return count_uses<c>( from, line_t< l/2 >{} )+ count_uses<c>( line_t< f+l/2>{}, line_t<(l+1)/2>{} );
}
#define UNIQUE(C) \
FLAG_USE(C) \
static_assert( count_uses<C>( line_t<0>{}, line_t<__LINE__+1>{} )==1, "too many" )
This should work in files of size 2^100s, until your compiler runs out of memory, as counting is log-depth recursion.
The type line_t enables deferred ADL lookup of use_flag until we invoke count_uses. We do a binary tree sum over every overload of use_flag, one per line per character in the file.
Live example.

How to adjust my function templates so that they can be "partially specialized"?

Now I know that partial specialization of function templates is something that doesn't exist (If I am understanding it correctly, the reasoning goes that if they did, they would never be used).
However I have a particular function template that I am using heavily now which goes like this:
template <typename>
char const* TypeName() {
return "UNDECLARED TYPENAME";
}
#define TYPENAME(...) \
GET_MACRO(__VA_ARGS__, TYPENAME32, TYPENAME31, TYPENAME30, TYPENAME29, \
TYPENAME28, TYPENAME27, TYPENAME26, TYPENAME25, TYPENAME24, \
TYPENAME23, TYPENAME22, TYPENAME21, TYPENAME20, TYPENAME19, \
TYPENAME18, TYPENAME17, TYPENAME16, TYPENAME15, TYPENAME14, \
TYPENAME13, TYPENAME12, TYPENAME11, TYPENAME10, TYPENAME9, \
TYPENAME8, TYPENAME7, TYPENAME6, TYPENAME5, TYPENAME4, TYPENAME3, \
TYPENAME2, TYPENAME1)(__VA_ARGS__)
#define TYPENAME1(x) \
template<> char const* TypeName<x>() { return #x; }
#define TYPENAME2(x, y) \
template<> char const* TypeName<x, y>() { return #x "," #y; }
#define TYPENAME3(x, y, z) \
template<> char const* TypeName<x, y, z>() { return #x "," #y "," #z; }
#define TYPENAME4(x, y, z, w) \
template<> char const* TypeName<x, y, z, w>() { return #x "," #y "," #z "," #w; }
// ... and so on and so forth
where GET_MACRO is
#define GET_MACRO(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,NAME,...) NAME
Okay, so this all works fine and dandy, so long as anywhere in my code I place e.g. TYPENAME(std::unordered_map<std::vector<typeA>, std::map<int, std::string>>) then I'll be able to have compile-time code evaluate TypeName<T>() where T is std::unordered_map<std::vector<typeA>, std::map<int, std::string>> to be "std::unordered_map<std::vector<typeA>, std::map<int, std::string>>". That works perfectly fine right now.
Just to clarify, the sole purpose of the TYPENAME macro is just so that I can avoid typing template <> char const* TypeName<...>() { return ... }. It's for DRYness. It is an elaborate set of macros because of the pesky commas that tend to be in types.
Now, I'd like to declare some templates that are not necessarily fully specified. For example I would like to have templates declared (through my macros) for TypeName<std::unordered_map<K,V>>() and also for TypeName<typeA>, and to have the evaluation of the former function call TypeName and TypeName to determine what to return for the string.
However it appears that I have run into a limit because I clearly am not supposed to be able to achieve this with function templates. It also appears that that entire set of macros need to just get thrown out the window if I want to do this sort of thing.
Can I do it with class templates? I do want to have this all work at compile-time, but if there is a way to do it so that a dynamic string is built and cached the first time it's accessed (at run-time), that should also be reasonably fast enough that I can deal with it.
I just want to not have to explicitly declare a template for the fully qualified type, I want TypeName to assemble the string intelligently (Like I already can do with e.g. operator <<).
I wonder if maybe I just did something really dumb and should just have made my typename T live as the function argument to TypeName rather than the template parametrization (as Herb Sutter says)? Still a little fuzzy on how that syntax would be written, though!
You should be able to do what you want with class template:
template <typename...> struct helper_typename;
#define TYPENAME1(x) \
template<> struct helper_typename<x> \
{ static char const* TypeName() { return #x; } };
#define TYPENAME2(x, y) \
template<> struct helper_typename<x, y> \
{ static char const* TypeName() { return #x "," #y; } };
//...
// partial specialization for std::unordered_map
template <typename K, typename V>
struct helper_typename<std::unordered_map<K, V>>
{
static char const* TypeName()
{
static std::string s = std::string("std::unordered_map<")
+ helper_typename<K>::TypeName() + ", "
+ helper_typename<V>::TypeName() + ">";
return s.c_str();
}
};
template <typename... Ts> char const* TypeName()
{
// forward to the correct (partial) specialized struct.
return helper_typename<Ts...>::TypeName();
}

compile-time counter for template classes

Imagine that you have a lot of classes with a lot of different template parameters. Every class has a method static void f(). You want to collect all these function pointers in a list L.
A run-time solution would be easy:
typedef void (*p)();
std::vector<p> L;
int reg (p x) { static int i = 0; L.push_back(x); return i++; } // also returns an unique id
template <typename T> struct regt { static int id; };
template <typename T> int regt<T>::id = reg (T::f);
template < typename ... T > struct class1 : regt< class1<T...> > { static void f(); };
template < typename ... T > struct class2 : regt< class2<T...> > { static void f(); };
// etc.
The compiler knows all f()s of all instantiated classes at compile-time. So, theoretically it should be possible to generate such a list (a const std::array<p, S> L with some S) as a compile-time constant list. But how? (C++0x solutions are welcome, too).
Why do I need this?
On an architecture with only 256 kB (for code and data), I need to generate objects for incoming ids of classes. Existing serialization frameworks or the run-time solution above are unnecessarily big. Without templates a compile-time solution would be easy, but I want to keep all the advantages templates offer.
Manually
The simplest thing that you can do is just roll the code manually, I don't think that there is much that can be used to your advantage from the templates, so I will use plain classes, where A, B... stand for particular instantiations of your types. That allows for compile time initialization of the types, at the cost of having to remember to update the lookup table whenever a new type is added to the system:
typedef void (*function_t)();
function_t func[] = {
&A::f,
&B::f,
&C::f
};
I would recommend this, from a maintenance point of view. Automating the system will make the code much harder to understand and maintain in the future.
Macros
The simple most automated one, which will probably generate less code is a macro generation system is just using macros. Since this first approach will use extensive use of macros, I will generate the functions automatically, as you did in the previous question. You can remove that part of code if you have (hopefully) given up the path of full code generation through macros.
To avoid having to retype the names of the types in different contexts you can define a macro with all the data you need for any context, and then use other macros to filter what is to be used (and how) in each particular context:
// This is the actual list of all types, the id and the code that you were
// generating in the other question for the static function:
#define FOREACH_TYPE( macro ) \
macro( A, 0, { std::cout << "A"; } ) \
macro( B, 1, { std::cout << "B"; } ) \
macro( C, 2, { std::cout << "C"; } )
// Now we use that recursive macro to:
// Create an enum and calculate the number of types used
#define ENUM_ITEM( type, id, code ) \
e_##type,
enum AllTypes {
FOREACH_TYPE( ENUM_ITEM )
AllTypes_count
};
#undef ENUM_ITEM
// Now we can create an array of function pointers
typedef void (*function_t)();
function_t func[ AllTypes_count ];
// We can create all classes:
#define CREATE_TYPE( type, the_id, code ) \
struct type {\
static const int id = the_id; \
static void func() code\
};
FOREACH_TYPE( CREATE_TYPE )
#undef CREATE_TYPE
// And create a function that will
#define REGISTER_TYPE( type, id, code ) \
func[ i++ ] = &type::func;
void perform_registration() {
int i = 0;
FOREACH_TYPE( REGISTER_TYPE );
};
#undef REGISTER_TYPE
// And now we can test it
int main() {
perform_registration();
for ( int i = 0; i < AllTypes_count; ++i ) {
func[ i ]();
}
}
This is, on the other hand a maintenance nightmare, quite fragile and hard to debug. Adding new types is trivial, just add a new line to the FOREACH_TYPE macro and you are done... and the best of lucks once something fails...
Templates and metaprogramming
On the other hand, using templates you can get close but you cannot get to the single point of definition for the types. You can automate some of the operations in different ways, but at the very least you will need to define the types themselves and add them to a typelist to get the rest of the functionality.
Simplifying the definition of the actual type_list with C++0x code you can start by defining the types and then creating the type_list. If you want to avoid using C++0x, then take a look at the Loki library, but with C++0x a type list is simple enough:
template <typename ... Args> type_list {}; // generic type list
typedef type_list< A, B, C, D > types; // our concrete list of types A, B, C and D
// this is the only source of duplication:
// types must be defined and added to the
// type_list manually [*]
Now we need to use some metaprogramming to operate on the type list, we can for example count the number of elements in the list:
template <typename List> struct size; // declare
template <typename T, typename ... Args> // general case (recursion)
struct size< type_list<T,Args...> > {
static const int value = 1 + size< type_list<Args...>::value;
};
template <> // stop condition for the recursion
struct size< type_list<> > {
static const int value = 0;
};
Having the size of the type list is a first step in our problem, as it allows us to define an array of functions:
typedef void (*function_t)(); // signature of each function pointer
struct registry {
static const int size = ::size< types >::value;
static const function_t table[ size ];
};
function_t registry::table[ registry::size ]; // define the array of pointers
Now we want to register the static functions from each particular type in that array, and for that we create an auxiliar function (encapsulated as a static function in a type to allow for partial specializations). Note that this concrete part is designed to be run during initialization: it will NOT be compile time, but the cost should be trivial (I would be more worried on the binary size with all the templates):
template <typename T, int N> // declaration
struct register_types_impl;
template <typename T, typename ... Args, int N> // general recursion case
struct register_types_impl< type_list<T,Args...>, N> {
static int apply() {
registry::table[ N ] = &T::f; // register function pointer
return register_types_impl< type_list<Args...>, N+1 >;
}
};
template <int N> // stop condition
struct register_types_impl< type_list<>, int N> {
static int apply() { return N; }
};
// and a nicer interface:
int register_types() {
register_types_impl< types, 0 >();
}
Now we need an id function that maps our types to the function pointer, which in our case is the position of the type in the type list
template <typename T, typename List, int N> // same old, same old... declaration
struct id_impl;
template <typename T, typename U, typename ... Args, int N>
struct id_impl< T, type_list<U, Args...>, N > { // general recursion
static const int value = id_impl< T, type_list<Args...>, N+1 >;
};
template <typename T, typename ... Args, int N> // stop condition 1: type found
struct id_impl< T, type_list<T, Args...>, N> {
static const int value = N;
};
template <typename T, int N> // stop condition 2: type not found
struct id_impl< T, type_list<>, N> {
static const int value = -1;
}
// and a cleaner interface
template <typename T, typename List>
struct id {
static const int value = id_impl<T, List, 0>::value;
};
Now you just need to trigger the registration at runtime, before any other code:
int main() {
register_types(); // this will build the lookup table
}
[*] Well... sort of, you can use a macro trick to reuse the types, as the use of macros is limited, it will not be that hard to maintain/debug.
The compiler knows all f()s of all instantiated classes at compile-time.
There's your mistake. The compiler knows nothing about template instantiations in other compilation units. It should now be pretty obvious why the number of instantiations isn't a constant integral expression that could be used as a template argument (and what if std::array was specialized? Halting Problem ahead!)