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&LT;&安培; S.M&GT; X4; //错误:非静态元素的地址
那么为什么标准明确禁止这一点,尽管全局变量的非静态成员的地址在编译时是唯一的以及已知的?
答案 0 :(得分:15)
首先,要使用指向子对象的指针/引用,您需要能够对其进行修改。这是一项非常艰巨的任务。
其次,可能更重要的是,来自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?
答案“是”是有问题的,因为有些事情你可以做 指向[
a.y
]的指针,如果有未定义的行为 在[a.x
]结尾处的指针上执行。答案“否”是 有问题,因为它们(在典型的实现中)代表了 同一地址。
答案 1 :(得分: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
。另一种方法是命名(mangle)没有静态类成员,它会剥离语言的动态功能。