C++:宏可以扩展“abc”吗?变成“a”、“b”、“c”?

发布于 2024-10-10 02:13:24 字数 468 浏览 3 评论 0 原文

我编写了一个可变参数模板,它接受可变数量的 char 参数,即

template <char... Chars>
struct Foo;

我只是想知道是否有任何宏技巧可以让我使用类似于以下的语法实例化它:

Foo<"abc">

Foo<SOME_MACRO("abc")>

Foo<SOME_MACRO(abc)>

基本上

,任何阻止你单独编写字符的东西,就像这样

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

这对我来说不是一个大问题,因为它只是一个玩具程序,但我想我还是会问。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

锦上情书 2024-10-17 02:13:24

我今天创建了一个,并在 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',但您仍然可以获得编译时字符串。

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.

北恋 2024-10-17 02:13:24

基于 Sylvain Defresne 的上述响应的解决方案在 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) 上正确编译。

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

烛影斜 2024-10-17 02:13:24

虽然进行了很多尝试,但我认为最终注定会失败。

要理解其中的原因,需要了解预处理器的工作原理。预处理器的输入可以被认为是一个流。该流首先在预处理令牌中进行转换(列表可在《C++ 编程语言》第 3 版,附录 A 语法,第 795 页中找到)

在这些令牌上,预处理器只能应用非常有限数量的操作,除了二元组/三元组的东西之外,这相当于:

  • 文件包含(对于头指令),据我所知,这可能不会出现在宏中
  • 宏替换(这是非常复杂的东西:p)
  • #:将标记转换为字符串文字标记(通过用引号将其括起来)
  • ##:连接两个标记

就是这样。

  • 没有预处理器指令可以将一个标记拆分为多个标记:这是宏替换,这意味着实际上首先定义了一个宏。
  • 没有预处理器指令可以将字符串文字转换为然后可以进行宏替换的常规标记(删除引号)。

因此,我认为这是不可能的(无论是在 C++03 还是 C++0x 中),尽管可能(可能)有编译器特定的扩展。

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.

慈悲佛祖 2024-10-17 02:13:24

基于上面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)。字符串常量的长度最多为 BOOST_PP_LIMIT_REPEAT 个字符。

作业:实现 push_front_tc_str 来获取 std::integer_sequence< /代码>。 (不过,您可以尝试使用Boost.MPL)

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)

错々过的事 2024-10-17 02:13:24

这曾经在 msvc 的早期版本中工作,我不知道它是否仍然有效:

#define CHAR_SPLIT(...) #@__VA_ARGS__

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

#define CHAR_SPLIT(...) #@__VA_ARGS__
趁年轻赶紧闹 2024-10-17 02:13:24

不幸的是,我相信这是不可能做到的。 Boost 提供了可以从预处理器获得的最佳效果。预处理器,最显着的是通过其数据类型:

  • array :语法为 (3, (a, b, c))
  • list :语法为 (a, (b, (c, BOOST_PP_NIL)))
  • sequence :语法为 (a)(b)(c)
  • tuple :语法为 (a, b, c)

从任何这些类型中,您都可以轻松创建一个宏,该宏将构建一个逗号分隔的单引号括起来的项目列表(例如,请参见 BOOST_PP_SEQ_ENUM),但我相信这个宏的输入必须是这些类型之一,所有这些都需要单独输入字符。

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.

淡写薰衣草的香 2024-10-17 02:13:24

根据我上面讨论的内容,以下可怕的模板黑客可能足以实现这一目标。我还没有测试过这个(抱歉!),但我很确定它或类似的东西可能会起作用。

第一步是构建一个仅包含字符元组的模板类:

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;
}

我们还假设我们有一个元 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;
};

我真的希望这能奏效。如果没有,我仍然认为这值得发布,因为它可能接近有效答案。 :-)

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

雨夜星沙 2024-10-17 02:13:24

在 C++14 中,这可以通过使用立即调用的 lambda 和静态成员函数来完成,类似于 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("someliteral") 返回的对象不能是 constexpr,因为 lambda 不能是 constexpr。
在 C++20 之前,您不能只编写 decltype(STR("someliteral")),因为在未计算的上下文中不允许使用 lambda。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文