40

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.

a3f
  • 8,517
  • 1
  • 41
  • 46
Peter Alexander
  • 53,344
  • 14
  • 119
  • 168
  • 1
    `"abc"` is essentially the same as `'a', 'b', 'c', '\0'`, except for pointer stuff. – Ignacio Vazquez-Abrams Jan 03 '11 at 08:32
  • It used to be the case that you couldn't instantiate a template in C++ using a raw C string if the template were parameterized over a char*. Did they fix that in C++0x? If so, I think I have a way of doing this expansion correctly. – templatetypedef Jan 03 '11 at 08:32
  • @Ignacio: I know that, but you can't write `"abc"` for a `char...` template argument. @templatetypedef: The template isn't parameterised over `char*`, it is a variadic template over `char...` – Peter Alexander Jan 03 '11 at 08:35
  • @Peter Alexander: True, true... but couldn't you build an auxiliary template class parameterized over a char* that exports the tuple, then make a macro that instantiates that auxiliary template, then extracts the tuple'd type out of it? That's kinda what I was thinking about. – templatetypedef Jan 03 '11 at 08:40
  • @templatetypedef: Interesting idea, but I don't think you can extract the tuple from the char* (please correct me if I'm wrong) – Peter Alexander Jan 03 '11 at 08:45
  • @Peter Alexander - I think you could do it with enough layers of indirection: template class CharTuple {}; Next, make something that exports it: template class StringToChars { typedef CharTuple* answer */> type; }; Next, make an adapter to convert from CharTuple to your type: template class Adaptor; template class Adaptor> { typedef Foo type; }; Finally, glue everything together: template FooWrapper { typedef typename Adaptor::type>::type type; }; – templatetypedef Jan 03 '11 at 08:50
  • @Peter Alexander- Whew! Ran out of characters there. Does that idea make sense? I needed to do something like this once to build a generic "bind" template once. – templatetypedef Jan 03 '11 at 08:51
  • @templatetypedef: I see what you're doing, but what lies in this mysterious /* answer */ code? :-) – Peter Alexander Jan 03 '11 at 08:53
  • @Peter Alexander- See below. :-) – templatetypedef Jan 03 '11 at 09:05
  • 2
    In C++0x n3225, the spec also allows `constexpr char index(char const *x, int n) { return x[n]; }`, I think. You could then say `int x[index("\x4\x5", 1)];` to create a `int[5]` for example. That's function invocation substitution. – Johannes Schaub - litb Jan 03 '11 at 22:01
  • http://stackoverflow.com/a/15912824/2097780 – kirbyfan64sos Jun 18 '15 at 21:09

8 Answers8

21

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.

Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • Interesting, but am I correct in saying this only handles up to length 18 strings? – Peter Alexander Jan 17 '11 at 07:27
  • 1
    @peter, yes. but you can just add more E'es. so not a real limitation. c.f. boost.pp – Johannes Schaub - litb Jan 17 '11 at 09:06
  • @litb: True, but the strings in my use case could easily go into thousands of characters :-) – Peter Alexander Jan 17 '11 at 09:10
  • @litb: Out of interest, do you know if the 4.6 trunk supports user-defined literals yet? That would put the whole thing to rest :-) – Peter Alexander Jan 17 '11 at 09:14
  • 3
    @Peter it doesn't support them. But even if it would: I have tried to think of a way to do it, but I don't think it's possible without macros. Passing `a, b, c` to a template is a syntax thing, so you need a macro no matter what. You *can* *process* a string at compile time with a user defined literal with a `constexpr` function, but the return type of that function cannot depend on the contents of the string literal. Merely the value of the return can. At least AFAICS. – Johannes Schaub - litb Jan 17 '11 at 09:40
  • @litb: That would be fine for my purposes (I think...) – Peter Alexander Jan 17 '11 at 13:19
  • 8
    @Peter: thousands of characters? Will that even compile. I'm sure you'll hit some internal compiler limit long before you get to a variadic template that large. – deft_code Jan 17 '11 at 19:35
  • It's been a while since you've posted code I have to glance over several times to get. – GManNickG Jan 21 '11 at 05:49
  • @litb, You could almost do this with `constexpr` functions and new style function declarations (`auto foo() -> int`). The one problem is `L` cannot be reference in the return type even if it's referenced in a constant way (eg with a constexpr function). I don't know if this is part the C++0x standard or if it is just an artifact of GCC's implementation. There may be a work around but I didn't find one. – deft_code Mar 02 '11 at 22:43
  • @deft_code this is part of the C++0x spec. Whatever you do, you will reference a function parameter in the end. That's not valid as a template parameter (for `constexpr auto f(int n) -> T;`, you get for example an error because `n` is not a valid template argument - it's a function parameter). – Johannes Schaub - litb Mar 02 '11 at 22:47
10

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).

user1653543
  • 101
  • 1
  • 3
9

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.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • This is unfortunate, but I think you're right. Something more powerful than a C++ preprocessor is required for such an operation. – Sylvain Defresne Jan 03 '11 at 22:00
  • The `#` is useful here. It would allow `FOO(a,b,c)` to be expanded to `'a','b','c'`. Use these two macros: `#define asChar(x) #x[0]` and `#define FOO(x,y,z) asChar(asChar(x)),asChar(asChar(y)),asChar(asChar(z))` Tested and it works. It's a pity this simple version is hardcoded to three chars. clang3.3, and g++-4.6, but I don't think it's using anything too fancy. If `"sdlkfj"[0]` evaluates to `'s'` at compile time, then it should work on any compiler. – Aaron McDaid Oct 28 '13 at 19:18
5

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)

Nevermore
  • 1,127
  • 9
  • 12
4

this used to work in an early version of msvc, I don't know if it still does:

#define CHAR_SPLIT(...) #@__VA_ARGS__
P47RICK
  • 136
  • 2
3

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.

icecrime
  • 74,451
  • 13
  • 99
  • 111
2

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. :-)

templatetypedef
  • 362,284
  • 104
  • 897
  • 1,065
  • I don't believe you can dereference `str` at compile time, but I'll give it a shot. – Peter Alexander Jan 03 '11 at 09:10
  • That's a valid point; I've never really tried doing anything like this before. Could you potentially have a template specialization of the type over the empty string? Or would that not work correctly either? – templatetypedef Jan 03 '11 at 09:12
  • Another idea (apologies if this is starting to get annoying) - could you somehow introspect on the length of the string at compile-time? If so, you could implement the StringToChars by counting down from the length of the string to zero. Using a macro might make this easier. With the new constexpr keyword this might be doable, but my understanding of C++0x isn't strong enough to know whether or not this is possible. – templatetypedef Jan 03 '11 at 09:17
  • Getting the size of the array is easy, but you can't use `[]` operator on strings at compile time either. – Peter Alexander Jan 03 '11 at 09:32
  • But can't you define an interface which gives you a tuple of ptr to chars? – Macke Jan 03 '11 at 12:51
1

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.

Justin
  • 24,288
  • 12
  • 92
  • 142
  • 1
    @AlexisWilke That's intentional. `SomeType::some_invalid_member` is one of many methods to get the compiler to output the name of a type. I generally choose `_` as the invalid member. – Justin Jun 26 '19 at 15:19