指向(数据)成员作为非类型模板参数的指针,例如具有自动存储持续时间/无链接

dfr*_*fri 5 c++ templates pointer-to-member

考虑以下片段:

#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();
}
Run Code Online (Sandbox Code Playgroud)

我的第一个猜测会一直说这是病态的,因为fcallMeWhenever()具有自动存储时间,它的地址是不是在编译时已知,而成员模板函数increaseCounter()Foo与指针的数据成员实例化Foo,以及存储表示给定类类型的特定于编译器(例如填充)。但是,从cppreference / Template parameters 和 template arguments,afaics,这是格式良好的:

模板非类型参数

实例化具有非类型模板参数的模板时,以下限制适用:

[..]

[直到 C++17 ] 对于指向成员的指针,参数必须是指向成员的指针,表示为&Class::Member或计算为空指针或std::nullptr_t值的常量表达式。

[..]

[自 C++17 起] 唯一的例外是引用或指针类型的非类型模板参数 [自 C++20 起添加:以及类的非类型模板参数中的引用或指针类型的非静态数据成员类型及其子对象 (C++20 起) ] 不能引用/成为

  • 一个子对象(包括非静态类成员、基子对象或数组元素);
  • 一个临时对象(包括在引用初始化期间创建的对象);
  • 字符串文字;
  • typeid 的结果;
  • 或预定义变量__func__

这是如何运作的?编译器(通过直接或间接,例如上述标准要求)是否需要自己解决这个问题,仅存储成员之间的(编译时)地址偏移量,而不是实际地址?

IE /例如,是编译时指向数据的指针构件非类型模板参数counterFoo::increaseCounter()(每两个特定指针数据成员instantations的)简单地编译时间地址的任何给定的实例化的偏移Foo,在以后将成为一个完全每个实例的解析地址Foo,即使是尚未分配的地址,例如fcallMeWhenever()?

Sto*_*ica 4

编译器(通过直接或间接,例如上述标准要求)是否需要自行对此进行排序,仅存储成员之间的(编译时)地址偏移量,而不是实际地址?

差不多了。即使在编译时上下文之外,它也是一个“偏移量”。指向成员的指针与常规指针不同。它们指定成员,而不是对象。这也意味着有关有效指针目标的用语与指向成员的指针无关。

这就是为什么要从它们中产生一个实际的左值,必须用引用对象的东西来完成图片,就像我们在 中所做的那样this->*counter。如果您尝试在this->*counter需要常量表达式的地方使用,编译器会抱怨,但它会是关于this, 而不是counter

它们独特的性质使得它们能够无条件地成为编译时间常量。编译器没有必须将任何对象检查为有效目标。