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

时间:2011-04-05 06:02:31

标签: c++ templates string-literals

以下引用来自Addison Wesley的 C ++模板。有人可以帮助我理解普通英语/外行人的条款其要点吗?

  

因为字符串文字是具有内部链接的对象(两个字符串文字具有相同的值但在不同的模块中是不同的对象),所以您不能将它们用作模板参数:

5 个答案:

答案 0 :(得分:47)

您的编译器最终会对名为translation units, informally called source files的内容进行操作。在这些翻译单元中,您可以识别不同的实体:对象,函数等。链接器作业是将这些单元连接在一起,并且该过程的一部分是合并标识。

标识符具有链接 内部链接表示该翻译单元中指定的实体仅对该翻译单元可见,而< em>外部链接表示该实体对其他单位可见。

当实体标记为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" 不同。每个翻译单元都会被赋予一个不同的类,因为每个"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

†并非所有标识符都有链接;有些没有,比如函数参数。

‡优化编译器将相同的文字存储在同一地址,以节省空间;但这是一个实施细节的质量,而不是保证。

答案 1 :(得分:11)

这意味着你不能这样做......

#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>();
...

答案 2 :(得分:7)

显然,像“foobar”这样的字符串文字与其他文字内置类型(如int或float)不同。他们需要一个地址(const char *)。地址实际上是编译器替代文字出现位置的常量值。该地址指向某个地方,在编译时修复,在程序的内存中。

因此必须具有内部联系。内部链接仅意味着不能跨翻译单元(已编译的cpp文件)进行链接。编译器可以尝试这样做,但不是必需的。换句话说,内部链接意味着如果你在不同的cpp文件中取两个相同的文字字符串的地址(即它们转换成的const char *的值),它们通常不会是相同的。

您不能将它们用作模板参数,因为它们需要strcmp()来检查它们是否相同。如果您使用了==,那么您只需要比较地址,当模板在不同的翻译单元中使用相同的文字字符串进行实例化时,这些地址就不一样了。

其他更简单的内置类型,如文字,也是内部链接(它们没有标识符,不能从不同的翻译单元链接在一起)。然而,他们的比较是微不足道的,因为它是有价值的。所以它们可以用于模板。

答案 3 :(得分:3)

如其他答案所述,字符串文字不能用作模板参数。 但是,有一种具有类似效果的解决方法,但“字符串”限制为四个字符。这是由multi-character constants引起的,正如链接中所讨论的那样,它可能相当不可移植,但是我的调试工作正常。

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模板参数,如this answer

答案 4 :(得分:0)

c ++标准的想法只允许模板的某些类型的参数是该参数应该是常量的并且在编译时是已知的,以便生成“专用类”代码。

对于这个特定情况: 当你创建字符串文字时,他们的地址是未知的,直到链接时间(编译后发生链接),因为不同翻译单元中的两个字符串文字是两个不同的对象(正如接受的答案所解释的那样)。编译发生时,我们不知道用于从模板类生成专用类代码的字符串文字的地址。