次の引用は、Addison Wesley による C++ テンプレートからのものです。誰かが平易な英語/素人の言葉でその要点を理解するのを手伝ってくれませんか?
文字列リテラルは内部リンケージを持つオブジェクトであるため(同じ値を持つが異なるモジュールにある 2 つの文字列リテラルは異なるオブジェクトです)、それらをテンプレート引数として使用することもできません。
次の引用は、Addison Wesley による C++ テンプレートからのものです。誰かが平易な英語/素人の言葉でその要点を理解するのを手伝ってくれませんか?
文字列リテラルは内部リンケージを持つオブジェクトであるため(同じ値を持つが異なるモジュールにある 2 つの文字列リテラルは異なるオブジェクトです)、それらをテンプレート引数として使用することもできません。
コンパイラは最終的に、翻訳単位と呼ばれるもの、非公式にソース ファイルと呼ばれるものを操作します。これらの翻訳単位内で、さまざまなエンティティ (オブジェクト、関数など) を識別します。リンカーの仕事は、これらの単位を接続することであり、そのプロセスの一部はアイデンティティをマージすることです。
識別子にはリンケージがあります†</sup>:内部リンケージは、その翻訳単位で指定されたエンティティがその翻訳単位にのみ表示されることを意味し、外部リンケージはエンティティが他の単位に表示されることを意味します。
エンティティが とマークされている場合、エンティティには内部リンケージstatic
が与えられます。したがって、次の 2 つの翻訳単位が与えられます。
// a.cpp
static void foo() { /* in a */ }
// b.cpp
static void foo() { /* in a */ }
これらのそれぞれはfoo
、それぞれの翻訳単位にのみ表示されるエンティティ (この場合は関数) を参照します。つまり、各翻訳単位には独自の がありfoo
ます。
ここに問題があります。文字列リテラルは と同じ型static const char[..]
です。あれは:
// str.cpp
#include <iostream>
// this code:
void bar()
{
std::cout << "abc" << std::endl;
}
// is conceptually equivalent to:
static const char[4] __literal0 = {'a', 'b', 'c', 0};
void bar()
{
std::cout << __literal0 << std::endl;
}
ご覧のとおり、リテラルの値はその翻訳単位の内部にあります。"abc"
たとえば、複数の翻訳単位で使用すると、それらはすべて異なるエンティティになります。‡</sup>
全体として、これは概念的に無意味であることを意味します。
template <const char* String>
struct baz {};
typedef baz<"abc"> incoherent;
"abc"
は翻訳単位ごとに異なるためです。「同じ」引数を提供したとしても、それぞれが異なるエンティティであるため、各翻訳単位には異なるクラスが与えられます。"abc"
言語レベルでは、これは、テンプレートの非型パラメーターが外部リンケージを持つエンティティへのポインターになる可能性があるということによって課されます。つまり、翻訳単位全体で同じエンティティを参照するものです。
だからこれは大丈夫です:
// good.hpp
extern const char* my_string;
// good.cpp
const char* my_string = "any string";
// anything.cpp
typedef baz<my_string> coherent; // okay; all instantiations use the same entity
†すべての識別子にリンケージがあるわけではありません。関数パラメーターなど、何もないものもあります。
‡ 最適化コンパイラは、スペースを節約するために、同一のリテラルを同じアドレスに保存します。ただし、これは実装の詳細の品質であり、保証ではありません。
それはあなたがこれを行うことができないことを意味します...
#include <iostream>
template <const char* P>
void f() { std::cout << P << '\n'; }
int main()
{
f<"hello there">();
}
..."hello there"
テンプレートを 1 回インスタンス化するために使用できる単一の整数値に解決されることが 100% 保証されていないため (ただし、ほとんどの優れたリンカーは、リンクされたオブジェクト全体ですべての使用箇所を折り畳み、単一のコピーで新しいオブジェクトを生成しようとします)文字列)。
ただし、extern 文字配列/ポインターを使用することはできます。
...
extern const char p[];
const char p[] = "hello";
...
f<p>();
...
明らかに、"foobar" のような文字列リテラルは、他のリテラルの組み込み型 (int や float など) とは異なります。アドレス (const char*) が必要です。アドレスは実際には、リテラルが表示される場所の代わりにコンパイラが代入する定数値です。そのアドレスは、コンパイル時に修正された、プログラムのメモリ内のどこかを指しています。
そのため、内部リンクでなければなりません。内部リンケージは、翻訳単位 (コンパイルされた cpp ファイル) 間でリンクできないことを意味します。コンパイラはこれを試みることができますが、必須ではありません。言い換えれば、内部リンケージとは、異なる cpp ファイルで 2 つの同一のリテラル文字列 (つまり、変換後の const char* の値) のアドレスを取得した場合、一般にそれらは同じではないことを意味します。
それらが同じであることを確認するために strcmp() が必要になるため、それらをテンプレート パラメーターとして使用することはできません。== を使用すると、アドレスを比較するだけになり、テンプレートが異なる翻訳単位で同じリテラル文字列でインスタンス化されている場合は同じではありません。
リテラルなどの他の単純な組み込み型も内部リンケージです (それらには識別子がなく、異なる翻訳単位から一緒にリンクすることはできません)。ただし、それらの比較は値によるものであるため、簡単です。したがって、テンプレートに使用できます。
他の回答で述べたように、文字列リテラルはテンプレート引数として使用できません。ただし、同様の効果を持つ回避策がありますが、「文字列」は 4 文字に制限されています。これは、リンクで説明されているように、おそらく移植性が低い複数文字の定数によるものですが、私のデバッグ目的では機能しました。
template<int32_t nFourCharName>
class NamedClass
{
std::string GetName(void) const
{
// Evil code to extract the four-character name:
const char cNamePart1 = static_cast<char>(static_cast<uint32_t>(nFourCharName >> 8*3) & 0xFF);
const char cNamePart2 = static_cast<char>(static_cast<uint32_t>(nFourCharName >> 8*2) & 0xFF);
const char cNamePart3 = static_cast<char>(static_cast<uint32_t>(nFourCharName >> 8*1) & 0xFF);
const char cNamePart4 = static_cast<char>(static_cast<uint32_t>(nFourCharName ) & 0xFF);
std::ostringstream ossName;
ossName << cNamePart1 << cNamePart2 << cNamePart3 << cNamePart4;
return ossName.str();
}
};
以下で使用できます。
NamedClass<'Greg'> greg;
NamedClass<'Fred'> fred;
std::cout << greg.GetName() << std::endl; // "Greg"
std::cout << fred.GetName() << std::endl; // "Fred"
私が言ったように、これは回避策です。私はこれが優れた、きれいな、移植可能なコードであるとは思いませんが、他の人にとっては役に立つかもしれません。別の回避策には、この回答のように、複数の char テンプレート引数が含まれる可能性があります。