问题 C ++ concat两个`const char`字符串文字


是否可以使用a连接两个字符串文字 constexpr?或者改写一下可以消除代码中的宏,如:

#define nl(str) str "\n"

int main()
{
  std::cout <<
      nl("usage: foo")
      nl("print a message")
      ;

  return 0;
}

更新:使用没有问题 "\n",但我想知道是否可以使用 constexpr 替换那些类型的宏。


4974
2017-11-08 15:36


起源

怎么了? "usage: foo\n" "print a message\n"? - R. Martinho Fernandes
可能最好用 std::endl 而不是 \n - Douglas Leeder
@ R.MartinhoFernandes甚至 "usage: foo\nprint a message\n"? - James Kanze
@Douglas可能不是。如果要打印换行符,为什么要打印换行符 和 冲洗? - R. Martinho Fernandes
@DouglasLeeder: std::endl 当你想要的时候过度使用 '\n'。所以我不认为 std::endl 应该用来代替 '\n'。 - Nawaz


答案:


  1. 是的,完全可以创建编译时常量字符串,并使用constexpr函数甚至运算符来操作它们。然而,

  2. 除了静态和线程持续时间对象之外,编译器不需要执行任何对象的常量初始化。特别是,临时对象(不是变量,并且具有小于自动存储持续时间的东西)不需要被恒定初始化,并且据我所知,没有编译器对数组执行此操作。参见3.6.2 / 2-3,它定义了常量初始化; 6.7.4,关于块级静态持续时间变量的更多措辞。这些都不适用于临时工,其寿命在12.2 / 3及之后定义。

因此,您可以通过以下方式实现所需的编译时连接:

static const auto conc = <some clever constexpr thingy>;
std::cout << conc;

但你无法使用它:

std::cout <<  <some clever constexpr thingy>;

更新:

但是你 能够 使它适用于:

std::cout << *[]()-> const {
             static constexpr auto s = /* constexpr call */;
             return &s;}()
          << " some more text";

但样板标点符号太难看了,不能让它成为一个有趣的小黑客。


(免责声明:IANALL,虽然有时候我喜欢在互联网上玩一个。所以标准中可能有一些与上述相矛盾的尘土飞扬的角落。)

(尽管有免责声明,并且由@DyP推动,我还增加了一些语言 - 律师引用。)


1
2017-11-08 19:25



你能指出我的标准说临时工具有动态存储持续时间吗?找不到.. - dyp
我添加6.7 / 4,因为我们在这里处理块范围变量。而这只是 许可证 执行本地静态存储持续时间变量的“早期”初始化(需要在第一次块输入之前初始化)。 - dyp
@DyP:12.2 / 3“作为评估全表达式(1.9)的最后一步,(词法上)包含创建它们的点,临时对象被销毁。”之后有一些例外情况,但没有任何条款可以使临时成为永久性的。 - rici
对我来说,动态存储持续时间(错误地?)与之相关 new 和 delete  - 我期望(有效地)所有编译器将临时放在堆栈上。 - dyp
@DyP:非常正确,我实际上是指“自动”,但12.2 / 3似乎说临时工的生命甚至更短。我找不到用于描述除此之外的临时对象生命周期的短语,因此我相应地编辑了响应。无论标准如何,这可能允许编译器不断初始化constexpr临时 - 否则用户定义的字符串文字将不那么有趣 - 我很确定编译器实际上不会这样做,除非是用户定义的字符串文字,尚未广泛实现。 - rici


一点儿 constexpr,撒上一些TMP和指数的顶部给我这个:

#include <array>

template<unsigned... Is> struct seq{};
template<unsigned N, unsigned... Is>
struct gen_seq : gen_seq<N-1, N-1, Is...>{};
template<unsigned... Is>
struct gen_seq<0, Is...> : seq<Is...>{};

template<unsigned N1, unsigned... I1, unsigned N2, unsigned... I2>
constexpr std::array<char const, N1+N2-1> concat(char const (&a1)[N1], char const (&a2)[N2], seq<I1...>, seq<I2...>){
  return {{ a1[I1]..., a2[I2]... }};
}

template<unsigned N1, unsigned N2>
constexpr std::array<char const, N1+N2-1> concat(char const (&a1)[N1], char const (&a2)[N2]){
  return concat(a1, a2, gen_seq<N1-1>{}, gen_seq<N2>{});
}

实例。

我会更多地将它充实,但我必须开始并想在此之前放弃它。你应该可以从中工作。


13
2017-11-08 17:31



我也考虑过这种方法,但由于“实施数量”(附件B),我使用了另一种方法。虽然我不是很确定,但我认为这会限制你可以使用的字符串长度为256(或1024)个字符,而字符串字符本身可以超过65k字符。 - dyp
@Dyp(和Xeo):这与DyP的聪明解决方案有同样的问题,即它在产生预期输出时,它实际上是在运行时创建字符串。为了不让它这样做,据我所知,你必须做类似的事情 static const auto s = _call to clever constexpr_。我用clang 3.2和g ++ 4.7.2编写了这两个(对'DyP's抱歉')来查看生成的汇编代码。 - rici
@rici哦,你是对的,谢谢(§3.6.2/ 2) - dyp
@DyP,事实上,我只是在重读那一节。它是Afaics 允许 编译器对临时工作进行不断的初始化,但它肯定不需要它们这样做,而且clang和gcc都没有。但是,标准中的其他文本很可能也会妨碍不断初始化(除了证明3.6.2 / 3中的限制不适用,这本身可能很棘手)。 - rici
该示例的链接似乎已被破坏 - NathanOliver


乍一看,C ++ 11用户定义的字符串文字似乎是一种更简单的方法。 (例如,如果您正在寻找一种在编译时全局启用和禁用换行注入的方法)


1
2017-11-08 15:52



等等,也许不是......文字连接不会保留边界:( - Ben Voigt


  • 您无法从函数返回(普通)数组。
  • 你不能创建一个新的 const char[n] 在constexpr中(§7.1.5/ 3 dcl.constexpr)。
  • 一个 地址常量表达式 必须引用静态存储持续时间的对象(§5.19/ 3 expr.const) - 这不允许使用类型对象的一些技巧,这些对象具有组装数组以进行连接的constexpr ctor,并且你的constexpr fct只是将它转换为ptr。
  • 传递给constexpr的参数不被认为是编译时常量,所以你也可以在运行时使用fct - 这不允许使用模板元编程的一些技巧。
  • 你不能将传递给函数的字符串文字的单个字符作为模板参数 - 这不允许其他一些模板元编程技巧。

所以(据我所知),你不能得到一个返回的constexpr char const* 一个新构造的字符串或一个 char const[n]。请注意,大多数这些限制不适用于 std::array 正如Xeo所指出的那样。

即使你可以归还一些 char const*,返回值不是文字,只连接相邻的字符串文字。这发生在翻译阶段6(§2.2),我仍称之为预处理阶段。 Constexpr稍后进行评估(参考?)。 (f(x) f(y) 哪里 f 是一个函数是语法错误afaik)

但是你可以从constexpr fct返回一个其他类型的对象(带有constexpr ctor或者是一个聚合),它包含两个字符串,可以插入/打印成一个 basic_ostream


编辑:这是一个例子。这是相当长的o.O. 请注意,您可以简化此操作,以获得额外的“\ n”添加字符串的结尾。 (这更像是我刚从内存中写下的通用方法。)

编辑2:实际上,你无法真正简化它。创造 arr 数据成员作为“const char_type数组”包含'\ n'(而不是字符串文字数组)使用一些花哨的可变参数模板代码实际上有点长(但它的工作原理,请参阅Xeo的答案)。

注意:as ct_string_vector (名称不好)存储指针,它应该只用于静态存储持续时间的字符串(例如文字或全局变量)。优点是不必通过模板机制复制和扩展字符串。如果使用constexpr来存储结果(如示例中所示) main),如果传递的参数不是静态存储持续时间,编译器应该抱怨。

#include <cstddef>
#include <iostream>
#include <iterator>

template < typename T_Char, std::size_t t_len >
struct ct_string_vector
{
    using char_type = T_Char;
    using stringl_type = char_type const*;

private:
    stringl_type arr[t_len];

public:
    template < typename... TP >
    constexpr ct_string_vector(TP... pp)
        : arr{pp...}
    {}

    constexpr std::size_t length()
    {  return t_len;  }

    template < typename T_Traits >
    friend
    std::basic_ostream < char_type, T_Traits >&
    operator <<(std::basic_ostream < char_type, T_Traits >& o,
        ct_string_vector const& p)
    {
        std::copy( std::begin(p.arr), std::end(p.arr),
            std::ostream_iterator<stringl_type>(o) );
        return o;
    }
};

template < typename T_String >
using get_char_type =
    typename std::remove_const < 
    typename std::remove_pointer <
    typename std::remove_reference <
    typename std::remove_extent <
        T_String
    > :: type > :: type > :: type > :: type;

template < typename T_String, typename... TP >
constexpr
ct_string_vector < get_char_type<T_String>, 1+sizeof...(TP) >
make_ct_string_vector( T_String p, TP... pp )
{
    // can add an "\n" at the end of the {...}
    // but then have to change to 2+sizeof above
    return {p, pp...};
}

// better version of adding an '\n':
template < typename T_String, typename... TP >
constexpr auto
add_newline( T_String p, TP... pp )
-> decltype( make_ct_string_vector(p, pp..., "\n") )
{
    return make_ct_string_vector(p, pp..., "\n");
}

int main()
{
    // ??? (still confused about requirements of constant init, sry)
    static constexpr auto assembled = make_ct_string_vector("hello ", "world");
    enum{ dummy = assembled.length() }; // enforce compile-time evaluation
    std::cout << assembled << std::endl;
    std::cout << add_newline("first line") << "second line" << std::endl;
}

1
2017-11-08 16:25



枚举不是必需的; static constexpr auto 会做的。事实上, static const auto 如果可能,将使其不断初始化。但是这些都不会让第二个std :: cout行以与OP中的宏相同的方式运行,甚至不能将add_newline(“第一行”)优化到编译时文字中。 - rici
@rici Sry,但我仍然不明白为什么 static constexpr 足够了。毕竟,这是一个块范围的静态变量,因此,6.7 / 4成立(“允许实现......”)。也许聊天(无法弄清楚如何启动o.O)? - dyp
@DyP:6.7 / 4表示“具有静态存储持续时间的块范围实体的常量初始化(3.6.2)(如果适用)在首次输入块之前执行。”因此,如果常量初始化适用,则应用它。 “允许实施...... 其他 块范围变量...“语句适用于3.6.2中条件的初始化 不要 应用。至少,这是我的解释,但就像我在免责声明中所说,IANALL。 - rici


不,为 constexpr 你首先需要一个合法的函数,函数不能粘贴字符串文字参数。

如果你考虑常规函数中的等价表达式,它将分配内存并连接字符串 - 绝对不适合 constexpr


0