C++:宏可以扩展“abc”吗?变成“a”、“b”、“c”?
我编写了一个可变参数模板,它接受可变数量的 char
参数,即
template <char... Chars>
struct Foo;
我只是想知道是否有任何宏技巧可以让我使用类似于以下的语法实例化它:
Foo<"abc">
或
Foo<SOME_MACRO("abc")>
或
Foo<SOME_MACRO(abc)>
基本上
,任何阻止你单独编写字符的东西,就像这样
Foo<'a', 'b', 'c'>
这对我来说不是一个大问题,因为它只是一个玩具程序,但我想我还是会问。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
我今天创建了一个,并在 GCC4.6.0 上进行了测试。
一些测试
因此,虽然您没有得到
'a', 'b', 'c'
,但您仍然可以获得编译时字符串。I've created one today, and tested on GCC4.6.0.
Some test
So while you don't get a
'a', 'b', 'c'
, you still get compile time strings.基于 Sylvain Defresne 的上述响应的解决方案在 C++11 中是可能的:
此外,如果有问题的模板能够处理多个终止 '\0' 字符,我们可以放宽长度要求以支持最大长度
:示例在 clang++ (3.2) 和 g++ (4.8.0) 上正确编译。
A solution based on Sylvain Defresne's response above is possible in C++11:
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:
The above examples compile properly on clang++ (3.2) and g++ (4.8.0).
虽然进行了很多尝试,但我认为最终注定会失败。
要理解其中的原因,需要了解预处理器的工作原理。预处理器的输入可以被认为是一个流。该流首先在预处理令牌中进行转换(列表可在《C++ 编程语言》第 3 版,附录 A 语法,第 795 页中找到)
在这些令牌上,预处理器只能应用非常有限数量的操作,除了二元组/三元组的东西之外,这相当于:
#:将标记转换为字符串文字标记(通过用引号将其括起来)
##
:连接两个标记就是这样。
因此,我认为这是不可能的(无论是在 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:
#
: transforms a token into a string-literal token (by surrounding it by quotes)##
: concatenates two tokensAnd that's it.
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.
基于上面user1653543的解决方案。
一些模板魔法:
一些 PP 魔法:
split_helper
只是剪切尾随零的助手。现在STRING("Hello")
是一个类型化的编译时 char 序列 (std::integer_sequence
)。字符串常量的长度最多为BOOST_PP_LIMIT_REPEAT
个字符。作业:实现
push_front_t
和c_str
来获取std::integer_sequence< /代码>。 (不过,您可以尝试使用Boost.MPL)
Based on user1653543's solution above.
Some template magic:
Some PP magic:
split_helper
just helper to cut trailing zeroes. NowSTRING("Hello")
is a typed compile-time char sequence (std::integer_sequence<char, 'H', 'e', 'l', 'l', 'o'>
). Length of string constants is up toBOOST_PP_LIMIT_REPEAT
characters.Homework: implement
push_front_t
andc_str
to get null-terminated string ofstd::integer_sequence<char, ...>
. (Although, you can try to use Boost.MPL)这曾经在 msvc 的早期版本中工作,我不知道它是否仍然有效:
this used to work in an early version of msvc, I don't know if it still does:
不幸的是,我相信这是不可能做到的。 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.根据我上面讨论的内容,以下可怕的模板黑客可能足以实现这一目标。我还没有测试过这个(抱歉!),但我很确定它或类似的东西可能会起作用。
第一步是构建一个仅包含字符元组的模板类:
现在,让我们构建一个可以将 C 样式字符串转换为 CharTuple 的适配器。为此,我们需要以下帮助器类,它本质上是元组的 LISP 风格缺点:
我们还假设我们有一个元 if 语句:
那么以下应该让您将 C 风格字符串转换为元组:
现在您可以将 C 样式字符串转换为字符元组,您可以通过此类型汇集输入字符串以恢复元组。不过,我们需要做更多的机械工作才能使其正常工作。 TMP不好玩吗? :-)
第一步是获取原始代码:
并使用一些模板专门化将其转换为
这只是另一层间接;而已。
最后,你应该能够做到这一点:
我真的希望这能奏效。如果没有,我仍然认为这值得发布,因为它可能接近有效答案。 :-)
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:
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:
Let's also assume we have a meta-if statement:
Then the following should let you convert a C-style string into a tuple:
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:
and use some template specialization to convert it to
It's just another layer of indirection; nothing more.
Finally, you should be able to do this:
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. :-)
在 C++14 中,这可以通过使用立即调用的 lambda 和静态成员函数来完成,类似于
BOOST_HANA_STRING
: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
: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.