37

以下引用来自Addison Wesley 的 C++ 模板。有人可以帮我用简单的英语/外行术语理解它的要点吗?

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

4

5 回答 5

53

您的编译器最终在称为翻译单元的东西上运行,非正式地称为源文件。在这些翻译单元中,您可以识别不同的实体:对象、函数等。链接器的工作是将这些单元连接在一起,并且该过程的一部分是合并身份。

标识符有链接†</sup>:内部链接意味着在该翻译单元中命名的实体仅对该翻译单元可见,而外部链接意味着该实体对其他单元可见。

当一个实体被标记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"在多个翻译单元中使用它们,它们最终都会成为不同的实体。‡</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

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

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

于 2011-04-05T06:24:26.657 回答
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>();
...
于 2011-04-05T06:18:58.133 回答
7

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

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

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

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

于 2011-04-05T06:28:04.930 回答
3

如其他答案所述,字符串文字不能用作模板参数。但是,有一种解决方法具有类似的效果,但“字符串”仅限于四个字符。这是由于多字符常量,如链接中所述,可能相当不可移植,但适用于我的调试目的。

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 模板参数,如本答案所示

于 2013-03-14T00:53:18.363 回答
0

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

对于这种特定情况:当您创建字符串文字时,它们的地址在链接时间之前是未知的(链接发生在编译之后),因为跨不同翻译单元的两个字符串文字是两个不同的对象(正如公认的答案所解释的那样)。当编译发生时,我们不知道使用哪个字符串文字的地址来从模板类生成专门的类代码。

于 2015-06-15T01:28:01.930 回答