为什么我不能将std::string用作非类型模板参数?

104
我明白非类型模板参数应该是一个常量整数表达式。有人能解释一下为什么是这样吗?
template <std::string temp>
void foo()
{
     // ...
}
error C2993: 'std::string' : illegal type for non-type template parameter 'temp'.
我理解什么是常量积分表达式。为什么不允许像上面的代码片段中的非常量类型std::string呢?

21
模板参数在编译时解析。 - Etienne de Martel
4个回答

129
你不能这样做的原因是非常量表达式无法在编译时解析和替换。它们可能会在运行时更改,这将需要在运行时生成一个新模板,而这是不可能的,因为模板是编译时的概念。
下面是标准允许的非类型模板参数(14.1 [temp.param] p4):
非类型模板参数必须具有以下(可选的cv限定符)类型之一:
- 整数或枚举类型, - 对象指针或函数指针, - 左值引用对象或左值引用函数, - 成员指针, - std::nullptr_t。

6
这属于“指向成员”的范畴。它可以是“指向成员函数”或“指向成员数据”。 - Xeo
7
值得注意的是,对于指向对象(或实例字段)的情况,这些对象必须具有静态存储期和链接(在C++11之前为外部链接,C++11中为内部或外部链接),以便可以在编译时创建对它们的指针。 - Theodore Murdock
4
在 C++20 中,只要类型具有强结构相等性、是字面量、没有可变/易变子对象,并且宇宙飞船运算符是公共的,那么现在允许这样做。 - Rakete1111
一个类型是否可以成为非类型模板参数与在运行时更改事物的问题是正交的;int变量可以更改,但int NTTPs不能。 - Davis Herring

80

不允许那样做。

然而,这是允许的:

template <std::string * temp> //pointer to object
void f();

template <std::string & temp> //reference to object
void g();

参见C++标准(2003)的§14.1/6,7,8。


示例:

template <std::string * temp> //pointer to object
void f()
{
   cout << *temp << endl;
}

template <std::string & temp> //reference to object
void g()
{
     cout << temp << endl;
     temp += "...appended some string";
}

std::string s; //must not be local as it must have external linkage!

int main() {
        s = "can assign values locally";
        f<&s>();
        g<s>();
        cout << s << endl;
        return 0;
}

输出:

can assign values locally
can assign values locally
can assign values locally...appended some string

7
因为模板基本上是在模板化那个 std::string 指针或引用对象的地址。如果那个变量是局部的,每次调用函数时可能会得到不同的地址。 - Xeo
11
@Mahesh: 在编译时,你无法知道调用栈的情况。在你的函数之前,可能已经调用了10个或3个或任何其他数量的函数,因此在堆栈上创建字符串的地址可能会因每次调用而改变。当你有一个具有外部链接的对象时,在编译/链接期间其地址是固定的。 - Xeo
2
@Xeo "它的地址在编译/链接期间是固定的。" 或者不是,对于可重定位或位置无关代码。 - curiousguy
1
这个回答(目前)似乎没有解决提问者的问题,他们想知道为什么会出现这种行为;这个回答只是重申了提问者的例子,没有提供任何解释。 - Quuxplusone
1
我晚了,看起来智能指针也不起作用。 - Nicholas Humphrey
显示剩余2条评论

31

你需要能够混淆模板参数

template <std::string temp>
void f() {
 // ...
}

f<"foo">();
f<"foo">(); // same function?

现在,一个实现需要为一个std::string或者任何其他用户定义的类创建一个独特的字符序列来存储某个特定值,但这个值的含义对于实现方来说是不知道的。此外,任意类对象的值都不能在编译时计算。

计划考虑允许按照常量表达式初始化的文字类类型作为后C++0x的模板参数类型(见下文)。这些可以通过递归地将数据成员按照其值编码(例如,对于基类,我们可以应用深度优先、从左到右的遍历)来解决。但对于任意类,这绝对行不通。


C++20开始,我们现在可以使用结构化的类类型作为模板参数。简而言之,结构化类必须具有constexpr构造函数、析构函数和仅包含结构化类型成员和基类(例如标量、其数组或引用)。它们还必须只有公共和非mutable基类和成员。如果将模板实例化为转换为参数类型的常量表达式,则允许编译器有意义地编码参数。


1
我们现在有一个非常类似于您描述的东西;您想编辑这个答案吗?因为它已经符合要求了。 - Davis Herring
@Davis,由于我对类类型模板参数的经验不足,我欢迎您添加相关更改。 - Johannes Schaub - litb
@Davis 我添加了一些文本。如果您能检查是否有不准确的地方,我将不胜感激! - Johannes Schaub - litb
谢谢更新;我只是稍微整理了一下。 - Davis Herring

10

在模板参数列表中提供的非类型模板参数是一个表达式,其值可以在编译时确定。这些参数必须是:

常量表达式、具有外部链接的函数或对象的地址,或静态类成员的地址。

另外,字符串字面量是具有内部链接的对象,因此不能将它们用作模板参数。你也不能使用全局指针。浮点字面量不允许使用,因为存在四舍五入误差的明显可能性。


2
你引用了一句话,那么它的来源是什么?如果这句话有来源,我想点赞它。 - Gabriel Staples

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接