Рассмотрим следующий фрагмент:
#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()
имеет автоматическое c время хранения и его адрес неизвестен во время компиляции, в то время как функция шаблона члена increaseCounter()
из Foo
создается с указателями на члены данных Foo
, а представление памяти данного типа класса задается компилятором c (например, заполнение) , Однако из cppreference / параметров шаблона и аргументов шаблона , на самом деле, это правильно:
Аргументы типа нетипизированные
Следующие ограничения применяются при создании экземпляров шаблонов, имеющих нетипичные параметры шаблона:
[..]
[ до C ++ 17 ] Для указателей на элементы: аргумент должен быть указателем на член, выраженным как &Class::Member
, или константным выражением, которое оценивается как нулевой указатель или значение std::nullptr_t
.
[..]
[ начиная с C ++ 17 ] Исключением является только то, что нетипичные параметры шаблона ссылки или тип указателя [ добавляются с C ++ 20: и non-stati c члены-данные ссылочного типа или типа указателя в нетиповом параметре шаблона типа класса и его подобъектов (начиная с C ++ 20) ] не могут ссылаться на / быть адресом из
- подобъекта (включая элемент класса non-stati c, базовый подобъект или элемент массива);
- временный объект (включая объект, созданный во время инициализации ссылки);
- строковый литерал;
- результат typeid;
- или предопределенная переменная
__func__
.
Как это работает? Обязан ли компилятор (по прямым или косвенным, например, указанным выше, стандартным требованиям) самому разбираться с этим, сохраняя только смещения адресов (во время компиляции) между членами, а не фактические адреса?
Т.е. / Например, является ли указатель времени компиляции на аргумент шаблона нетипичного элемента данных counter
в Foo::increaseCounter()
(для каждого из двух указанных указателей c на экземпляры элементов данных) просто смещение адреса времени компиляции для любого данного экземпляра Foo
, который впоследствии станет полностью разрешенным адресом для каждого экземпляра Foo
, даже для еще не выделенных, таких как f
в области действия блока callMeWhenever()
?