非静态成员的地址为什么不能作为模板非类型参数?

27
template <int* ip> struct test {};

struct q {
    static int a;
    int b;
    
    constexpr q(int b_) : b(b_) {}
};

int i;
constexpr q q0(2);

int main()
{
    constexpr test<&i> t1;      // Works fine
    constexpr test<&q::a> t2;   // Works 
    constexpr test<&q0.b> t3;   // Does not work; address of non-static member?
    
    return 0;
}
在上述代码中,尽管模板参数&q0.b在编译时已知,但是t3的声明失败。一些搜索结果表明,这在标准(第14.3.2节)中是不允许的:

[注意:数组元素的地址以及非静态类成员的名称或地址都不是可接受的模板参数。

X<&s.m> x4; // 错误:非静态成员的地址

那么为什么标准明确禁止此操作,尽管全局变量的非静态成员的地址也是唯一并且在编译时已知呢?


我在gcc 5.1.1和clang 3.5.0上尝试了这个,使用了-std=c++11。 - nav
它的类型不是 int*,而应该是 int q::*,是吧? - skypjack
@DieterLücking:编译器既然知道 i 的地址,为何不知道 q0 和 q0.b 的地址呢? - nav
1
@ShafikYaghmour 我删除了它,因为问题更多地是“为什么存在这个限制”,而不是“为什么这段代码无法编译?” 无论如何,这两种措辞都是不允许的。 - Barry
2
模板参数必须进行名称重整,而对子对象进行(任意复杂的)名称重整是一个非常棘手的问题。 - T.C.
显示剩余8条评论
2个回答

16

首先,要使用指向子对象的指针/引用,您需要能够编码它们。这是一项相当大的任务。

其次,更重要的是,来自N4198

为避免与指向子对象的指针产生别名问题,保留了常量表达式必须命名完整对象的限制:

struct A { int x, y; } a;
template<int*> struct Z;
using B = Z<&a.x + 1>;
using C = Z<&a.y>;
// Are B and C the same type?

引用Richard Smith的话:

"是"这个答案有问题,因为你可以对指向 [a.x] 结尾后的指针进行的某些操作,在指向 [a.y] 的指针上执行将会产生未定义行为。"否"这个答案也有问题,因为它们(在典型实现中)表示相同的地址。


+,主要因素简而言之。 - Columbo
1
@T.C. 如果x和y是静态成员或简单的全局变量,我们不会遇到相同的情况吗?(我不是C++专家,只是想理解一下 :)) - nav
@T.C. 你引用Richard Smith的话似乎提供了更合理的论点:“请记住,模板参数必须是在编译时可以合理确定并且可以被搅乱的东西。通用指针在编译时不知道(运行时链接器可以移动东西),并且并非所有平台都有明确的规则来搅乱它们。” - Thinkeye

1

用这段代码替换你的主要代码

int main(void)
{    
constexpr static int bb = 5;    
constexpr test<&bb> t;    
return 0;
}

你会收到错误信息:bb不能用作模板参数,因为它没有链接(不要与链接器相关的内容混淆)。

除非通过对象引用,否则无法访问类数据成员,并且在模板实例化期间无法考虑数据成员,因为数据成员没有链接,即它们不是定义的符号,因此不能用作模板参数。

话虽如此,通过readelf您可以验证这一点:

48: 00000000004006ac     4 OBJECT  LOCAL  DEFAULT   14 q0


68: 000000000060097c     0 NOTYPE  GLOBAL DEFAULT  ABS __bss_start
69: 0000000000600978     4 OBJECT  GLOBAL DEFAULT   23 q::a

但是没有定义 q0.b。另一种方法是给非静态类成员命名(混淆),这将剥夺语言的动态能力。

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