【发布时间】:2020-01-09 13:43:11
【问题描述】:
考虑以下 sn-p:
#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();
}
我的第一个猜测是这是格式错误的,因为callMeWhenever() 中的f 具有自动存储持续时间,并且它的地址在编译时是未知的,而@ 的成员模板函数increaseCounter() 987654327@ 使用指向Foo 数据成员的指针进行实例化,并且给定类类型的内存表示是编译器特定的(例如填充)。但是,来自cppreference / Template parameters and template arguments,afaics,这是格式正确的:
模板非类型参数
在实例化具有非类型模板参数的模板时适用以下限制:
[..]
[直到 C++17] 对于指向成员的指针,参数必须是指向成员的指针,表示为
&Class::Member或计算结果为空指针或std::nullptr_tvalue 的常量表达式.[..]
[自 C++17] 唯一的例外是引用或指针类型的非类型模板参数 [自 C++20: 和非静态数据成员添加类类型及其子对象的非类型模板参数中的引用或指针类型(C++20 起)] 不能引用/作为地址
- 子对象(包括非静态类成员、基子对象或数组元素);
- 一个临时对象(包括在引用初始化期间创建的一个);
- 字符串文字;
- typeid 的结果;
- 或预定义变量
__func__。
这是如何工作的?编译器(通过直接或间接,例如上述标准要求)是否需要自行对其进行排序,仅存储成员之间的(编译时)地址偏移量,而不是实际地址?
即/例如,是指向 Foo::increaseCounter() 中的数据成员非类型模板参数 counter 的编译时间指针(对于指向数据成员实例的两个特定指针中的每一个),只是任何给定的编译时间地址偏移量Foo 的实例化,稍后将成为 Foo 的每个实例的完全解析地址,即使在 callMeWhenever() 的块范围内尚未分配诸如 f 之类的地址?
【问题讨论】:
-
引用的“since C++17”部分看起来很可疑。有关信息,最新的draft of the Standard 允许指针类型的非类型模板参数是非静态类成员的地址。
-
@YSC 谢谢;我从引用中省略了 C++20 部分,可能令人困惑:" 以及类类型的非类型模板参数及其子对象中的引用或指针类型的非静态数据成员(自 C++20 起)".用完整的报价更新了我的问题。
标签: c++ templates pointer-to-member