C ++:macros可以将“abc”扩展为“a”,“b”,“c”吗?
我写了一个variadic模板,接受可变数量的char
参数,即
template <char... Chars> struct Foo;
我只是想知道是否有任何macros技巧,可以让我用类似于以下语法来实例化这个:
Foo<"abc">
要么
Foo<SOME_MACRO("abc")>
要么
Foo<SOME_MACRO(abc)>
等等
基本上,任何阻止你单独写字符的东西,就像这样
Foo<'a', 'b', 'c'>
这对我来说不是一个大问题,因为它只是一个玩具程序,但我想我会问。
我今天创build了一个,并在GCC4.6.0上testing。
#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; };
一些testing
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")()); }
所以,虽然你没有得到'a', 'b', 'c'
,你仍然得到编译时间的string。
有很多的尝试,但最终注定要失败,我认为。
要理解为什么,需要了解预处理器如何工作。 预处理器的input可以被认为是一个stream。 这个stream首先被转换成预处理标记 (在The C ++ Programming Language,3rd Edition,Annexe A Grammar,795页)
在这些令牌上,预处理器除了二进制/三字符之外,可能只应用非常有限的操作数量,
- 文件包含(对于标题指令),据我所知,这可能不会出现在macros中
- macros观替代(这是非常复杂的东西:P)
-
#
:将一个标记转换为一个string标记(通过用引号包围它) -
##
:连接两个令牌
就是这样。
- 没有预处理器指令可以将一个标记分成几个标记:这是macrosreplace,这意味着实际上首先定义一个macros
- 没有预处理器指令来将string文字转换成可以接受macrosreplace的正则标记(除去引号)。
因此,我认为这是不可能的(无论是在C ++ 03还是C ++ 0x),尽pipe可能(可能)是编译器特定的扩展。
基于Sylvain Defresne的上述响应的解决scheme在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'>
此外,如果所讨论的模板能够处理多个终止的'\ 0'字符,我们可以减less长度要求,以支持最大长度:
#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', ...>
上面的例子在clang ++(3.2)和g ++(4.8.0)上正确编译。
这个曾经在msvc的早期版本中工作,我不知道它是否仍然如此:
#define CHAR_SPLIT(...) #@__VA_ARGS__
不幸的是,我相信这是做不到的。 Boost.Preprocessor提供了最好的预处理器,最值得注意的是它的数据types:
-
array
:语法将是(3, (a, b, c))
-
list
:语法将是(a, (b, (c, BOOST_PP_NIL)))
-
sequence
:语法将是(a)(b)(c)
-
tuple
:语法将是(a, b, c)
从这些types中的任何一种,都可以很容易地创build一个macros,这个macros可以build立一个逗号分隔的单引号括起来的列表(参见例如BOOST_PP_SEQ_ENUM
),但是我相信这个macros的input必须是这些types之一,都需要单独input字符。
根据我上面讨论的内容,下面这个可怕的模板hackery可能就足够了。 我没有testing过这个(对不起!),但我很确定它或接近它的东西可能会工作。
第一步是build立一个只包含一个字符元组的模板类:
template <char... Chars> class CharTuple {};
现在,我们来构build一个可以将C风格的string转换为CharTuple的适配器。 要做到这一点,我们需要下面的帮助类,它本质上是一个LISP风格的元组:
template <typename Tuple, char ch> class Cons; template <char... Chars, char ch> class Cons<CharTuple<Chars... ch>> { typedef CharTuple<ch, Chars...> type; }
我们还假设我们有一个meta-if语句:
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; };
然后,下面应该让你转换一个C风格的string到一个元组:
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; };
现在,您可以将C风格的string转换为字符的元组,您可以通过此types获取inputstring以恢复元组。 尽pipe如此,我们还需要做更多的工作。 TMP不好玩吗? 🙂
第一步是采取你的原始代码:
template <char... Chars> class Foo { /* ... */ };
并使用一些模板专业化将其转换为
template <typename> class FooImpl; tempalte <char... Chars> class FooImpl<CharTuple<Chars...>> { /* ... */ };
这只是另一个间接的层面; 而已。
最后,你应该能够做到这一点:
template <char* str> class Foo { typedef typename FooImpl<typename StringToChars<str>::type>::type type; };
我真的希望这个工程。 如果没有,我仍然认为这值得张贴,因为它可能ε-接近于有效的答案。 🙂
基于上面的user1653543的解决scheme。
一些模板魔法:
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;
一些PP魔术:
#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
只是帮手削减尾随零。 现在STRING("Hello")
是一个types化的编译时char序列( std::integer_sequence<char, 'H', 'e', 'l', 'l', 'o'>
)。 string常量的长度最多为BOOST_PP_LIMIT_REPEAT
字符。
作业 :实现push_front_t
和c_str
以获得std::integer_sequence<char, ...>
以空字符结尾的string。 (虽然,你可以尝试使用Boost.MPL)