16

是否可以使用 a 连接两个字符串文字constexpr?或者换一种说法,可以消除代码中的宏,例如:

#define nl(str) str "\n"

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

  return 0;
}

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

4

5 回答 5

15

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

活生生的例子。

我会再充实一些,但我必须开始并想在那之前把它放下。你应该能够从中工作。

于 2012-11-08T17:31:03.873 回答
1
  • 您不能从函数返回(普通)数组。
  • 您不能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*一个新构造的字符串的 a 或 a char const[n]。请注意,这些限制中的大多数并不适用于std::arrayXeo 所指出的。

即使您可以返回 some char const*,返回值也不是文字,并且只有相邻的字符串文字被连接起来。这发生在翻译阶段 6(第 2.2 节)中,我仍将其称为预处理阶段。Consexpr 稍后评估(参考?)。(函数f(x) f(y)在哪里f是语法错误afaik)

但是您可以从您的 constexpr fct 返回包含两个字符串并且可以插入/打印到basic_ostream.


编辑:这是示例。它有点长 oO 请注意,您可以简化它,以便在字符串末尾添加一个额外的“\n”。(这更像是我刚刚从记忆中写下来的通用方法。)

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

注意:由于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;
}
于 2012-11-08T16:26:08.153 回答
1
  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 推动,我添加了更多的语言律师引用。)

于 2012-11-08T19:25:50.377 回答
1

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

于 2012-11-08T15:52:05.090 回答
0

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

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

于 2012-11-08T16:25:31.797 回答