请考虑以下代码段:
#include <cstdint>
#include <iostream>
struct Foo {
Foo() : foo_(0U), bar_(0U) {}
void increaseFoo() { increaseCounter<&Foo::foo_>(); }
void increaseBar() { increaseCounter<&Foo::bar_>(); }
template <uint8_t Foo::*counter>
void increaseCounter() { ++(this->*counter); }
uint8_t foo_;
uint8_t bar_;
};
void callMeWhenever() {
Foo f; // automatic storage duration, no linkage.
f.increaseFoo();
f.increaseFoo();
f.increaseBar();
std::cout << +f.foo_ << " " << +f.bar_; // 2 1
}
int main() {
callMeWhenever();
}
[我的第一个猜测是格式错误,因为f
中的callMeWhenever()
具有自动存储时间,并且在编译时不知道其地址,而increaseCounter()
的成员模板函数Foo
用指向Foo
的数据成员的指针实例化],并且给定类类型的内存表示形式是编译器特定的(例如,填充)。但是,从cppreference / Template parameters and template arguments,fasics,这是格式正确的:
模板非类型参数
实例化具有非类型模板参数的模板时,以下限制适用:
[..]
[直到C ++ 17]对于指向成员的指针,参数必须是指向以
&Class::Member
表示的成员的指针,或者是计算为空指针或std::nullptr_t
值的常量表达式。[..]
[自C ++ 17起>>]唯一的例外是引用或指针类型的非类型模板参数[自C ++ 20开始添加:并且引用或指针类型的非静态数据成员在类类型及其子对象的非类型模板参数(因为C ++ 20)]不能引用/是的地址
- 子对象(包括非静态类成员,基础子对象或数组元素);
- 一个临时对象(包括在引用初始化期间创建的对象;];>
- 字符串文字;
- typeid的结果;
- 或预定义变量
__func__
。这是如何工作的?是否要求编译器(通过直接或间接(例如,上述标准要求))自行解决此问题,只在成员之间存储(编译时)地址偏移量,而不是实际地址?
即/例如,是指向
counter
中数据成员非类型模板参数Foo::increaseCounter()
的编译时间指针(针对两个特定的指向数据成员实例的指针中的每一个),仅是任何给定实例的编译时间地址偏移量Foo
的值,该地址以后将成为Foo
的每个实例的完全解析的地址,即使尚未分配的地址(例如f
的块范围内的callMeWhenever()
)也是如此?
请考虑以下代码段:#include
是否需要编译器(通过直接或间接,例如上述标准要求)自行解决此问题,仅在成员之间存储(编译时)地址偏移量,而不是实际地址?