Concat两个`const char`字符串文字

时间:2012-11-08 15:36:12

标签: c++ c++11 c-preprocessor constexpr string-literals

是否可以使用constexpr连接两个字符串文字?换句话说,可以消除代码中的宏,如:

#define nl(str) str "\n"

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

  return 0;
}

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

5 个答案:

答案 0 :(得分:13)

一点点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>{});
}

Live example.

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

答案 1 :(得分:1)

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

答案 2 :(得分:1)

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

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

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

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


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

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

注意:由于ct_string_vector(名称不好)存储指针,因此它应仅用于静态存储持续时间的字符串(例如文字或全局变量)。优点是不必复制字符串&amp;通过模板机制扩展。如果使用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;
}

答案 3 :(得分:1)

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

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

  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推动,我还增加了一些语言律师引用。)

答案 4 :(得分:0)

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

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

相关问题