web-dev-qa-db-ja.com

非型パラメーターとしてconstchar *を使用したテンプレートトリック

2つの異なる変換単位で定義された2つの同一の文字列リテラルが異なるアドレスを持つ可能性があるため(ほとんどの場合、コンパイラは同じものを使用しますが)、テンプレートの非型パラメータとしてconst char*を直接渡すことは誤りであることをよく知っています。住所)。使用できるトリックがあります。以下のコードを参照してください。

#include <iostream>

template<const char* msg>
void display()
{
    std::cout << msg << std::endl;
}

// need to have external linkage 
// so that there are no multiple definitions
extern const char str1[] = "Test 1"; // (1)

// Why constexpr is enough? Does it have external linkage?
constexpr char str2[] = "Test 2";    // (2)

// Why doesn't this work? 
extern const char* str3 = "Test 3";  // (3) doesn't work

// using C_PTR_CHAR = const char* const;   // (4) doesn't work either
extern constexpr C_PTR_CHAR str4 = "Test 4"; 

int main()
{
    display<str1>();    // (1')
    display<str2>();    // (2')
    // display<str3>(); // (3') doesn't compile 
    //display<str4>();  // (4') doesn't compile
}

基本的に(1)で宣言し、define外部リンケージを持つ配列を宣言します。これは(1 ')のテンプレートパラメーターとして使用できます。私はこれをよく理解しています。しかし、私は理解していません:

  1. constexprバージョン(2)が機能するのはなぜですか? constexprには外部リンケージがありますか?そうでない場合は、同じ文字列リテラルを別の翻訳単位で定義すると、テンプレートのインスタンス化が重複する可能性があります。

  2. (3)と(4)が機能しないのはなぜですか?私にとっては完全に合理的なようですが、コンパイラはそうは信じていません。

    エラー:「str3」は変数であり、変数のアドレスではないため、「str3」は有効なテンプレート引数ではありません

22
vsoftco

1。簡単な答え:静的ストレージ期間でオブジェクトを定義しているため、constexprと宣言されているかどうかに関係なく機能します(は文字列リテラルではありません-1つの内容のコピーを格納します)、そのアドレスは定数式です。リンケージに関しては、str2には内部リンケージがありますが、それは問題ありません。そのアドレスは、型以外のテンプレート引数として使用できます。

長い答え:

C++ 11および14では、[14.3.2p1]は次のように述べています。

template-argument非型、非テンプレートの場合template-parameterは次のいずれかになります。
[...]

  • 関数テンプレートと関数を含む、静的な保存期間と外部または内部のリンケージ、または外部または内部のリンケージを持つ関数の完全なオブジェクトのアドレスを指定する定数式(5.19)template-id s非静的クラスメンバー。(括弧を無視して)&id-expressionとして表されます。ここで、id-expressionは、オブジェクトまたは関数の名前です。名前が関数または配列を参照している場合は&を省略でき、対応するtemplate-parameterが参照である場合は省略できます。

[...]

したがって、静的な保存期間を持つオブジェクトのアドレスを使用できますが、オブジェクトはリンク(内部または外部)を持つ名前で識別される必要があり、そのアドレスを表現する方法は制限されます。 (文字列リテラルは名前ではなく、リンクもありません。)

つまり、char str1[] = "Test 1";でも機能します。 static char str1[] = "Test 1";も問題ありません。 GCC 5.1.0はそれを拒否しますが、それはバグだと思います。 Clang3.6.0はそれを受け入れます。


str2のリンケージについて、C++ 11と14 [3.5p3]は次のように述べています。

名前空間スコープ(3.3.6)を持つ名前は、次の名前の場合、内部リンケージがあります。
[...]

  • 明示的にconstまたはconstexprと宣言され、明示的にexternと宣言されておらず、以前に外部リンケージがあると宣言されていない不揮発性変数。

[...]

N4431は、 DR 1686 の結果として、これをわずかに変更しました。

  • 明示的にexternと宣言されておらず、外部リンケージを持つように以前に宣言されていない、不揮発性のconst修飾型の変数。

constexprがオブジェクトのconst-qualificationを意味するという事実を反映しています。


2。簡単な答え:C++ 11および14については、上記を参照してください。ドラフトC++ 1zの場合、ポインタ自体はconstexprではなく、文字列リテラルのアドレスでもあるため、str3は定数式ではありません。 str4は定数ですが、それでも文字列リテラルのアドレスです。

長い答え:

現在の作業ドラフトであるN4431では、型以外のテンプレート引数の制約が緩和されています。 [14.3.2p1]は次のように述べています。

非型の場合はtemplate-argumenttemplate-parameterは、template-parameterの型の変換された定数式(5.20)でなければなりません。 。参照またはポインタ型の非型template-parameterの場合、定数式の値は参照してはなりません(または、ポインタ型の場合は、のアドレスであってはなりません)。

  • サブオブジェクト(1.8)、
  • 一時オブジェクト(12.2)、
  • 文字列リテラル(2.13.5)、
  • typeid式(5.2.8)の結果、または
  • 事前定義された__func__変数(8.4.1)。

そして、それらはすべて制限です。 変換された定数式の部分は非常に重要です。完全な定義はlongですが、この場合に関連する部分の1つは、静的ストレージ期間を持つオブジェクトのアドレスがそのような式であるということです。

また、[5.20p2.7]によると、左辺値から右辺値への変換に適用されることも関連しています。

constexprで定義された不揮発性オブジェクトを参照する、またはそのようなオブジェクトの非可変サブオブジェクトを参照する不揮発性glvalue

また、定数式であるための条件を満たす。これにより、いくつかのconstexprポインター変数を非型テンプレート引数として使用できます。 (変数constを宣言するだけでは不十分であることに注意してください。これは、非定数式で初期化できるためです。)

したがって、constexpr const char* str3 = str1;のようなもので問題ありません。 C++ 1zモードのClang3.6.0で受け入れられます(C++ 14モードでは拒否されます)。 GCC5.1.0はまだそれを拒否します-更新されたルールをまだ実装していないようです。


それでも、文字列リテラルの何が問題になっていますか?問題は次のとおりです(N4431 [2.13.5p16]):

string-literalを評価すると、静的な保存期間を持つ文字列リテラルオブジェクトが生成され、上記で指定された文字から初期化されます。すべての文字列リテラルが別個であるか(つまり、重複しないオブジェクトに格納されているか)、string-literalの連続評価で同じオブジェクトが生成されるか、異なるオブジェクトが生成されるかは指定されていません。

実装では、文字列リテラルを使用して多くのことを実行できます。たとえば、混合、一致、オーバーラップ(全体的または部分的)、同じ翻訳単位からの7つのコピーなどです。これにより、文字列リテラルのアドレスを非型テンプレート引数として使用できなくなります。

15
bogdan