字符串文字不允许作为非类型模板参数

以下引用来自Addison Wesley的C ++模板。有人可以帮助我用简单的英语/外行人的术语来理解它的要点吗?   因为字符串文字是具有内部链接的对象(两个字符串文字具有相同的值但在不同的模块中是不同的对象),所以您不能将它们用作模板参数:     
已邀请:
您的编译器最终运行在称为翻译单元的东西上,非正式地称为源文件。在这些翻译单元中,您可以识别不同的实体:对象,函数等。链接器作业是将这些单元连接在一起,并且该过程的一部分是合并标识。 标识符具有链接†:内部链接意味着该翻译单元中指定的实体仅对该翻译单元可见,而外部链接表示该实体对其他单元可见。 当一个实体被标记为
static
时,它被赋予内部联系。所以考虑到这两个翻译单元:
// 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"
,它们最终都会成为不同的实体。‡ 总的来说,这意味着这在概念上毫无意义:
template <const char* String>
struct baz {};

typedef baz<"abc"> incoherent;
因为每个翻译单元的
"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"
不能100%保证解析为可用于实例化模板一次的单个整数值(尽管大多数好的链接器将尝试折叠链接对象的所有用法并生成具有单个副本的新对象的字符串)。 但是,您可以使用外部字符数组/指针:
...
extern const char p[];
const char p[] = "hello";
...
    f<p>();
...
    
显然,像“foobar”这样的字符串文字与其他文字内置类型(如int或float)不同。他们需要一个地址(const char *)。地址实际上是编译器替代文字出现位置的常量值。该地址指向某个地方,在编译时修复,在程序的内存中。 因此,它必须是内部联系。内部链接仅意味着不能跨翻译单元(已编译的cpp文件)进行链接。编译器可以尝试这样做,但不是必需的。换句话说,内部链接意味着如果你在不同的cpp文件中取两个相同的文字字符串的地址(即它们转换成的const char *的值),它们通常不会是相同的。 您不能将它们用作模板参数,因为它们需要strcmp()来检查它们是否相同。如果您使用了==,那么您只需要比较地址,当模板在不同的翻译单元中使用相同的文字字符串进行实例化时,这些地址就不一样了。 其他更简单的内置类型,如文字,也是内部链接(它们没有标识符,不能从不同的翻译单元链接在一起)。然而,他们的比较是微不足道的,因为它是有价值的。所以它们可以用于模板。     
如其他答案中所述,字符串文字不能用作模板参数。 但是,有一种具有类似效果的解决方法,但“字符串”限制为四个字符。这是由于多字符常量,如链接中所讨论的,可能相当不可移植,但我的调试目的是有效的。
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模板参数,如本答案中所述。     
只允许模板的某些类型的参数的c ++标准的想法是该参数应该是常量的并且在编译时是已知的,以便生成“专用类”代码。 对于这个特定情况: 当你创建字符串文字时,他们的地址是未知的,直到链接时间(编译后发生链接),因为不同翻译单元中的两个字符串文字是两个不同的对象(正如接受的答案所解释的那样)。编译发生时,我们不知道用于从模板类生成专用类代码的字符串文字的地址。     

要回复问题请先登录注册