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

Posted

技术标签:

【中文标题】字符串文字不允许作为非类型模板参数【英文标题】:String literals not allowed as non type template parameters 【发布时间】:2011-07-29 16:54:13 【问题描述】:

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

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

【问题讨论】:

我删除了c++-faq 标签。如果您认为有必要,请随时解释您认为有必要的原因。 @sbi 你在跟我说话吗?如果是,那么让我告诉你,我添加的唯一标签是“模板”。 “因为字符串字面量是具有内部链接的对象(两个字符串字面量相同但在不同的模块中是不同的对象),你也不能将它们用作模板参数”,这是一个有缺陷的C++0x 的推理,所以你最好把它从你的脑海中拿出来,以备将来的 C++ 工作。模板参数现在可以有内部链接。您可以改为说“因为字符串文字不匹配任何允许的模板参数形式......”。 @GMan 它的格式不正确:它不是一个整数常量表达式,它不是模板参数,也不是指向具有链接(内部或外部)的对象或函数的指针或引用表示为& id-expressionid-expression,它不是一个指向成员的指针,表示为& qualified-id等。 @Anisha:正如你所知,C++0x 是计划于今年发布的 C++ 的新版本。简单地说,他们这样做是为了让你可以使用任何指针值,只要它有一个name。字符串文字没有名称。 【参考方案1】:

您的编译器最终会在名为 translation units, informally called source files 的东西上运行。在这些翻译单元中,您可以识别不同的实体:对象、函数等。链接器的工作是将这些单元连接在一起,而该过程的一部分是合并身份。

标识符有链接内部链接表示该翻译单元中命名的实体仅对该翻译单元可见,而external links 表示实体对其他单位可见。

当一个实体被标记为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

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

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

【讨论】:

“翻译单元”不是规范名称,“源文件”不是非正式/非官方名称吗? @jalf:哎呀,是的,把我的话划掉了。 @GMan:您可能希望将此链接添加到您的答案中:***.com/questions/2795443/… 谢谢你的详细解答。我从您的回答中了解到,双引号中的字符串始终等同于静态字符数组。静态字符数组属于“内部链接”,模板属于“外部链接”。因此两者是不相容的。 请确认我的理解是否清楚 @Anisha:您的“因此”松散地暗示 - 通常 - 具有内部链接和外部链接的事物是“不兼容的”:那不是真的。恕我直言,关键点是相同的双引号字符串文字,出现在程序的不同翻译单元中,最终可能会在多个内存区域中重新创建,因此可以有多个不同的指针值指向相同的文本内容。这意味着不必要或意外的模板实例化导致代码膨胀,并且模板特化没有可靠匹配,因此 C++ 出于安全考虑不允许这样做。【参考方案2】:

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

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

【讨论】:

我不知道第二种形式是可能的,即使在当前标准中也是如此? @Nim: 是的,奇怪但真实 :-) @Anisha:整数,如整数、1、2、3。模板参数必须是类型或整数;这就是为什么枚举、字符、short/int/long 等都可以,但 float 和 double 不是,实际对象也不是。这里的要点是,具有编译时间常数值的指针是一个整数,并且“外部”变量满足该要求,而“静态”变量 - 它们实际上隐藏在当时正在编译的对象中,所以不要'没有它们的整数地址“广播”供链接器与其他对象拼接在一起,不能使用。另见 GMan 的回答 ;-) 在第二种形式中,p 不需要两个声明。 extern char const p[] = "..." 做得很好。 @Anisha:哦,他们确实有联系:-)...见***.com/questions/3281925/…【参考方案3】:

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

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

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

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

【讨论】:

非常感谢您对术语的详细解释。我进一步搜索了谷歌以使概念更加清晰。我了解外部链接意味着位于此标题下的对象可以与其他源文件中的其他对象进行通信。示例:在 stdio.h 中声明的 printf 可以在任何其他源文件中使用。现在,模板属于外部链接。那是对的吗?因为两个相同的字符串在不同的cpp文件中会有不同的地址,所以不能用它们来指代同一个东西。这就是为什么他们不被允许。 @Anisha:听起来你已经明白了...... :-) @Tony:我没有看 GMan 的帖子就写了这个解释。现在我正在阅读他的帖子,这个概念现在更加清晰。 :)【参考方案4】:

正如其他答案中提到的,字符串文字不能用作模板参数。 但是,有一种解决方法具有类似的效果,但“字符串”仅限于四个字符。这是由于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。

【讨论】:

【参考方案5】:

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

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

【讨论】:

以上是关于字符串文字不允许作为非类型模板参数的主要内容,如果未能解决你的问题,请参考以下文章

C++初阶---array forward_list 模板进阶

在“char 类型”模板化类中使用字符串文字

在 C++ 模板中发送字符串文字作为参数 - 参数包

取决于模板类型参数的字符串文字?

字符串文字取决于模板类型参数?

c++ 模板