16

?を使用して2つの文字列リテラルを連結することは可能constexprですか?言い換えれば、次のようなコードでマクロを削除できますか?

#define nl(str) str "\n"

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

  return 0;
}

更新:を使用しても問題はありませんが、これらのタイプのマクロを置き換えるために"\n"使用できるかどうかを知りたいです。constexpr

4

5 に答える 5

15

少しconstexprTMPを振りかけ、インデックスをトッピングすると、次のようになります。

#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

一見すると、C++11のユーザー定義の文字列リテラルははるかに単純なアプローチのように見えます。(たとえば、コンパイル時に改行インジェクションをグローバルに有効または無効にする方法を探している場合)

于 2012-11-08T15:52:05.090 に答える
1
  • 関数から(プレーンな)配列を返すことはできません。
  • const char[n]constexpr(§7.1.5/ 3dcl.constexpr)内に新しいものを作成することはできません。
  • アドレス定数式は、静的ストレージ期間のオブジェクトを参照する必要があります(§5.19/ 3expr.const)-これにより、constexpr ctorが配列を連結用にアセンブルし、constexprfctがそれをptrに変換するタイプのオブジェクトでのトリックを禁止します。 。
  • constexprに渡される引数はコンパイル時の定数とは見なされないため、実行時にfctを使用することもできます。これにより、テンプレートメタプログラミングでのいくつかのトリックが許可されなくなります。
  • テンプレート引数として関数に渡される文字列リテラルの単一の文字を取得することはできません。これにより、他のテンプレートメタプログラミングのトリックが許可されなくなります。

したがって(私が知る限り)、char const*新しく構築された文字列またはを返すconstexprを取得することはできませんchar const[n]std::arrayXeoが指摘しているように、これらの制限のほとんどは当てはまらないことに注意してください。

また、いくつかを返すことができたとしてもchar const*、戻り値はリテラルではなく、隣接する文字列リテラルのみが連結されます。これは、翻訳フェーズ6(§2.2)で発生します。これは、私がまだ前処理フェーズと呼んでいます。Constexprは後で評価されます(参照?)。(f(x) f(y)ここfで、関数は構文エラーafaikです)

ただし、constexpr fctから、両方の文字列を含み、に挿入/印刷できる他のタイプのオブジェクト(constexpr ctorまたは集合体を使用)を返すことができますbasic_ostream


編集:これが例です。かなり長いですoO文字列の末尾に「\n」を追加するためだけに、これを合理化できることに注意してください。(これは、私がメモリから書き留めたばかりのより一般的なアプローチです。)

Edit2:実際、あなたはそれを本当に合理化することはできません。arr(文字列リテラルの配列ではなく)「\ n」が含まれる「constchar_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. コンパイラは、static-durationオブジェクトとthread-durationオブジェクト以外のオブジェクトの一定の初期化を実行する必要はありません。特に、一時オブジェクト(変数ではなく、自動保存期間よりも短いもの)は、定数で初期化する必要はありません。私が知る限り、配列に対してこれを行うコンパイラーはありません。定数の初期化を定義する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 に答える
0

いいえ、constexprそもそも正当な関数が必要であり、関数は文字列リテラル引数の貼り付けなどを行うことができません。

通常の関数での同等の式について考えると、メモリを割り当てて文字列を連結することになります。これは、絶対に受け入れられませんconstexpr

于 2012-11-08T16:25:31.797 に答える