#опытным
В прошлый раз мы уже выяснили, что явно инстанцируя шаблонный метод класса, можно написать свою реализацию, которая будет жонглировать непубличными членами в самых виртуозных позах.
Но!
Мы так и не вышли за пределы класса. Ручная специализация шаблонного метода - это такой же метод класса, поэтому он и умеет трогать приватные поля.
Хочется прям снаружи получить доступ к полю и уже не ограничиваться реализацией метода.
С++ и это может воплотить в реальность.
Хоть стандарт и бьет по рукам за упоминание имен непубличных членов за пределами скоупа класса, все-таки есть исключения из правил:
[temp.spec.partial.general]/10
The usual access checking rules do not apply to non-dependent names used to specify template arguments of the simple-template-id of the partial specialization.
[temp.spec.general]/6
The usual access checking rules do not apply to names in a declaration of an explicit instantiation or explicit specialization
Если по-человечески, то проверка доступа к имени не проверяется при явной специализации и инстанциации шаблона. То есть:
class Foo {
private:
int data = 42;
};
template <auto V>
struct Bar {};
template struct Bar<&Foo::data>;Этот код вполне влиден. Здесь мы используем указатель на поле класса
Foo::data в качестве NTTP. Это валидно, потому что указатель на поле класса - это по сути смещение от начала объекта и оно известно на момент компиляции.Однако, даже если вы явно инстанцируете шаблон, содержащий приватные типы, вы не сможете создать такой объект напрямую.
Bar<&Foo::data> b;
// error: 'int Foo::data' is private within this context
Выход заключается в том, чтобы сохранить значение указателя в статическом члене и передать его в другой класс.
template <typename PtrType>
struct Storage {
inline static PtrType ptr;
};
template <auto V>
struct PtrTaker {
struct Transferer {
Transferer() {
Storage<decltype(V)>::ptr = V;
}
};
inline static Transferer tr;
};
template struct PtrTaker<&Foo::data>;
Когда вы явно инстанцируете
PtrTaker<&Foo::data>, его статический член tr будет инициализирован, и в его конструкторе Storage<PtrType>::ptr получит значение. Теперь вы можете получить доступ к нему через:Foo foo;
std::cout << foo.Storage<int Foo::>::ptr;
Это работает!
То есть мы просто взяли и вывели на консоль значение приватного члена класса, при этом никак не меняя его код. Можно также легко его изменить. И все это четко согласовано со стандартом.
Еще один раз шаблоны сломали инкапсуляцию. Да что ж это такое!
Спасибо, @SoulslikeEnjoyer, за материалы для поста)
Exploit loopholes. Stay cool.
#cppcore #template #fun