C ++: Может ли макрос расширять «abc» в «a», «b», «c»? - PullRequest
39 голосов
/ 03 января 2011

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

template <char... Chars>
struct Foo;

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

Foo<"abc">

или

Foo<SOME_MACRO("abc")>

или

Foo<SOME_MACRO(abc)>

и т.д.

По сути, все, что мешает вам писать символы по-отдельности, например

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

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

Ответы [ 8 ]

19 голосов
/ 17 января 2011

Я создал его сегодня и протестировал на 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', вывсе еще получите строки времени компиляции.

9 голосов
/ 07 сентября 2012

Решение, основанное на ответе Сильвена Дефресна, выше, возможно в 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', мы можем ослабитьтребование длины в пользу максимальной длины:

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

9 голосов
/ 03 января 2011

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

Чтобы понять почему, нужно понять, как работает препроцессор. Ввод препроцессора можно рассматривать как поток. Этот поток сначала преобразуется в токены предварительной обработки (список доступен на языке программирования C ++, 3-е издание, приложение A, грамматика, стр. 795)

На этих токенах препроцессор может применять только очень ограниченное количество операций, кроме диграммы / триграммы, это составляет:

  • включение файла (для директив заголовка), насколько я знаю, это может не отображаться в макросе
  • подстановка макросов (что очень сложно: p)
  • #: преобразовывает токен в строковый литерал токен (окружая его кавычками)
  • ##: объединяет два токена

И это все.

  • Нет инструкции препроцессора, которая может разбить токен на несколько токенов: это подстановка макросов, что означает, что на самом деле макрос определен в первую очередь
  • Нет инструкции препроцессора для преобразования строкового литерала в обычный токен (без кавычек), который затем может быть подвергнут макросам.

Поэтому я утверждаю, что это невозможно (в C ++ 03 или C ++ 0x), хотя для этого могут (возможно) быть специфичные для компилятора расширения.

4 голосов
/ 26 июня 2015

Основано на решении 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;

Немного магии ПП:

#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") - это типизированная последовательность символов времени компиляции (std::integer_sequence<char, 'H', 'e', 'l', 'l', 'o'>). Длина строковых констант - до BOOST_PP_LIMIT_REPEAT символов.

Домашнее задание : реализовать push_front_t и c_str, чтобы получить строку с нулевым символом в конце std::integer_sequence<char, ...>. (Хотя вы можете попробовать использовать Boost.MPL)

4 голосов
/ 03 января 2011

раньше работало в ранней версии msvc, я не знаю, работает ли она до сих пор:

#define CHAR_SPLIT(...) #@__VA_ARGS__
3 голосов
/ 03 января 2011

К сожалению, я считаю, что это невозможно сделать. Лучшее, что вы можете получить от препроцессора, это 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), но я считаю, что ввод этого макрос должен быть одним из этих типов, и все они требуют, чтобы символы вводились индивидуально.

2 голосов
/ 03 января 2011

Исходя из того, что я обсуждал выше, следующий ужасный взлом шаблонов может быть достаточным, чтобы осуществить это. Я не проверял это (извините!), Но я уверен, что это или что-то похожее может сработать.

Первым шагом является создание шаблонного класса, который просто содержит кортеж из символов:

template <char... Chars> class CharTuple {};

Теперь давайте создадим адаптер, который может преобразовывать строку в стиле C в 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; : -)

0 голосов
/ 16 февраля 2019

В C ++ 14 это можно сделать с помощью немедленной лямбды и статической функции-члена, аналогично 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

До C ++ 17 объект, возвращаемый STR("some literal"), не может быть constexpr, потому что лямбда не может быть constexpr. До C ++ 20 нельзя просто написать decltype(STR("some literal")), потому что лямбды не допускаются в неоцененных контекстах.

...