С++: Может ли макрос расширять "abc" на "a", "b", "c"?
Я написал вариационный шаблон, который принимает переменное число параметров char
, т.е.
template <char... Chars>
struct Foo;
Мне просто интересно, были ли какие-либо макро-трюки, которые позволили бы мне создать экземпляр этого синтаксиса, подобный следующему:
Foo<"abc">
или
Foo<SOME_MACRO("abc")>
или
Foo<SOME_MACRO(abc)>
и др.
В принципе, все, что мешает вам писать персонажи в отдельности, например
Foo<'a', 'b', 'c'>
Это не большая проблема для меня, как для игрушечной программы, но я думал, что все равно прошу.
Ответы
Ответ 1
Я создал его сегодня и тестировал на 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;
};
Некоторые тесты
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'
, вы все равно получаете строки времени компиляции.
Ответ 2
Прошло много испытаний, но в конечном итоге оно обречено на провал, я думаю.
Чтобы понять, почему, нужно понять, как работает препроцессор. Вход препроцессора можно рассматривать как поток. Этот поток сначала преобразовывается в токеты предварительной обработки (список доступен на языке программирования С++, 3-е издание, грамматика Приложения A, стр. 795)
В этих токенах препроцессор может применять только очень ограниченное число операций, кроме данных о цифрах/триграммах, эту сумму:
- включение файла (для директив заголовков), это может не отображаться в макросе, насколько я знаю
- макроподстановка (что чрезвычайно сложно: p)
-
#
: преобразует токен в токен-литеральный токен (окружая его кавычками)
-
##
: объединяет два токена
И что это.
- Нет инструкции препроцессора, которая может разделить токен на несколько токенов: это макроподстановка, что означает, что на самом деле есть макрос, определенный в первую очередь
- Нет инструкции препроцессора для преобразования строкового литерала в обычный токен (удаление котировок), который затем может быть заменен макросом.
Поэтому я утверждаю, что это невозможно (либо в С++ 03, либо в С++ 0x), хотя для этого могут быть (возможно) специальные расширения для компилятора.
Ответ 3
Решение, основанное на ответе Сильвена Defresne выше, возможно в С++ 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", мы можем облегчить требование длины в пользу максимальной длины:
#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).
Ответ 4
Это использовалось для работы в ранней версии msvc, я не знаю, все ли это делает:
#define CHAR_SPLIT(...) #@__VA_ARGS__
Ответ 5
К сожалению, я считаю, что этого не может быть сделано. Лучшее, что вы можете получить от препроцессора, обеспечивается Boost.Preprocessor, в первую очередь через его типы данных:
-
array
: синтаксис будет (3, (a, b, c))
-
list
: синтаксис будет (a, (b, (c, BOOST_PP_NIL)))
-
sequence
: синтаксис будет (a)(b)(c)
-
tuple
: синтаксис будет (a, b, c)
Из любого из этих типов вы можете легко создать макрос, который будет создавать список с отдельной запятой, заключенный в одну запятую (см., например, BOOST_PP_SEQ_ENUM
), но я считаю, что ввод этого макроса должен быть одним из этих типов, и все требуют, чтобы символы вводились индивидуально.
Ответ 6
Основываясь на том, что я обсуждал выше, может потребоваться следующий ужасный шаблонный хакер, чтобы снять это. Я не тестировал это (извините!), Но я уверен, что это или что-то близкое к нему может работать.
Первым шагом является создание класса шаблона, который просто содержит кортеж символов:
template <char... Chars> class CharTuple {};
Теперь создадим адаптер, который может преобразовать строку стиля С в 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 в кортеж:
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-стиля в кортеж символов, вы можете направить строку ввода через этот тип, чтобы восстановить кортеж. Тем не менее, нам нужно сделать немного больше механизмов для этого. Разве это не 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;
};
Я действительно надеюсь, что это сработает. Если это не так, я все же думаю, что это стоит опубликовать, потому что это, вероятно, & epsilon; -close для действительного ответа.: -)
Ответ 7
На основе вышеприведенного решения user1653543.
Некоторая магия шаблона:
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")
- это типизированная последовательность char времени компиляции (std::integer_sequence<char, 'H', 'e', 'l', 'l', 'o'>
). Длина строковых констант составляет BOOST_PP_LIMIT_REPEAT
.
Домашнее задание: реализовать push_front_t
и c_str
, чтобы получить строку с нулевым завершением std::integer_sequence<char, ...>
. (Хотя, вы можете попробовать использовать Boost.MPL)