Подтвердить что ты не робот

С++: Может ли макрос расширять "abc" на "a", "b", "c"?

Я написал вариационный шаблон, который принимает переменное число параметров char, т.е.

template <char... Chars>
struct Foo;

Мне просто интересно, были ли какие-либо макро-трюки, которые позволили бы мне создать экземпляр этого синтаксиса, подобный следующему:

Foo<"abc">

или

Foo<SOME_MACRO("abc")>

или

Foo<SOME_MACRO(abc)>

и др.

В принципе, все, что мешает вам писать персонажи в отдельности, например

Foo<'a', 'b', 'c'>

Это не большая проблема для меня, как для игрушечной программы, но я думал, что все равно прошу.

4b9b3361

Ответ 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)