Consider the following snippet:
#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();
}
My first guess would've been that this was ill-formed, as f
in callMeWhenever()
has automatic storage duration, and its address is not known at compile time, whereas the member template function increaseCounter()
of Foo
is instantiated with pointers to data members of Foo
, and the memory representation of a given class type is compiler specific (e.g. padding). However, from cppreference / Template parameters and template arguments, afaics, this is well-formed:
Template non-type arguments
The following limitations apply when instantiating templates that have non-type template parameters:
[..]
[until C++17] For pointers to members, the argument has to be a pointer to member expressed as
&Class::Member
or a constant expression that evaluates to null pointer orstd::nullptr_t
value.[..]
[since C++17] The only exceptions are that non-type template parameters of reference or pointer type [added since C++20: and non-static data members of reference or pointer type in a non-type template parameter of class type and its subobjects (since C++20)] cannot refer to/be the address of
- a subobject (including non-static class member, base subobject, or array element);
- a temporary object (including one created during reference initialization);
- a string literal;
- the result of typeid;
- or the predefined variable
__func__
.
How does this work? Is the compiler (by direct or indirect, e.g. the above, standard requirements) required to sort this out by itself, storing only (compile-time) address offsets between the members, rather than actual addresses?
I.e./e.g., is the compile time pointer to the data member non-type template argument counter
in Foo::increaseCounter()
(for each of the two specific pointer to data member instantations) simply a compile time address offset for any given instantiation of Foo
, that will later become a fully resolved address for each instance of Foo
, even for yet to be allocated ones such as f
in the block scope of callMeWhenever()
?