Intro
In most contexts, private members are forbidden to access outside its class. But as the title says, I’ll show you a way to validly access them. Valid, here, I mean it’s well-defined according to the standard, and not causing undefined behavior. This post is inspired by a post more than 10 years ago. It introduces this technique but doesn’t explain why.
What does the standard say?
There’re two places where private are allowed to be used outside its enclosing class,
[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.
[Note 2: The template arguments can be private types or objects that would normally not be accessible. …
The usual access checking rules do not apply to names in a declaration of an explicit instantiation or explicit specialization,
[Note 1: In particular, the template arguments and names used in the function declarator (including parameter types, return types and exception specifications) can be private types or objects that would normally not be accessible. — end note]
Simply saying, private types are allowed to be used in template arguments in specialization and explicit instantiations.
Say I have a class,
class Foo {
private:
int data = 42;
};
Then, this is okay to do so,
template <auto V >
struct Bar {};
template struct Bar<&Foo::data>;
Here, the type of non-type template parameter V
is a member pointer int Foo::*
The Magic
However, even though you explicitly instantiate a template containing private types, you could not construct such an object. The way out is to store the value of the pointer in a static member and transfer it to another class.
Here we go, a simple version looks like this,
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>;
When you explicit PtrTaker<&Foo::data>
, its static member tr
will be initialized, in whose constructor, Storage<PtrType>::ptr
is assigned. Now you can access it through,
Foo foo;
std::cout << foo.*Storage<int Foo::*>::ptr;
One problem here is if Foo
has more than one private member of the same type, you can only access the one PtrTaker
later instantiated. To solve this problem, you must create a tag for every private member. Since you have a tag, you can hide the pointer type inside the tag but not in direct usage.
Improved version,
template <typename Tag>
struct Storage {
inline static typename Tag::type ptr;
};
template <typename Tag, typename Tag::type V>
struct PtrTaker {
struct Transferer {
Transferer() {
Storage<Tag>::ptr = V;
}
};
inline static Transferer tr;
};
To create a tag,
struct FooTag1 {
using type = int Foo::*;
};
struct FooTag2 {
using type = int Foo::*;
};
And then,
Foo foo;
std::cout << foo.*Storage<FooTag1>::ptr;
Function members are similar to data members, so I don’t expand it here. If you have an interest in using this trick in your project, take a look at this repo, which provides some useful utilities.