5

This question is somewhat a continuation of this one I've posted.

What I was trying to do: my point was to allow access to private members of a base class A in a derived class B, with the following restraints:

  • what I want to access is a structure -- an std::map<>, actually --, not a method;
  • I cannot modified the base class;
  • base class A has no templated method I may overload as a backdoor alternative -- and I would not add such method, as it would be going against the second restraint.

As a possible solution, I have been pointed to litb's solution (post / blog), but, for the life of me, I haven't been able to reach an understanding on what is done in these posts, and, therefore, I could not derive a solution to my problem.

What I am trying to do: The following code, from litb's solution, presents an approach on how to access private members from a class/struct, and it happens to cover the restrictions I've mentioned.

So, I'm trying to rearrange this one code:

template<typename Tag, typename Tag::type M>
struct Rob { 
  friend typename Tag::type get(Tag) {
    return M;
  }
};

// use
struct A {
  A(int a):a(a) { }
private:
  int a;
};

// tag used to access A::a
struct A_f { 
  typedef int A::*type;
  friend type get(A_f);
};

template struct Rob<A_f, &A::a>;

int main() {
  A a(42);
  std::cout << "proof: " << a.*get(A_f()) << std::endl;
}

To something that would allow me to do the following -- note I'm about to inherit the class, as the entries in the std::map<> are added right after the initialization of the derived class B, i.e., the std::map<> isn't simply a static member of class A with a default value, so I need to access it from a particular instance of B:

// NOT MY CODE -- library <a.h>
class A {
private:
    std::map<int, int> A_map;
};

// MY CODE -- module "b.h"
# include <a.h>
class B : private A {
public:
    inline void uncover() {
        for (auto it(A_map.begin()); it != A_map.end(); ++it) {
            std::cout << it->first << " - " << it->second << std::endl;
        }
    }
};

What I'd like as an answer: I'd really love to have the above code working -- after the appropriate modifications --, but I'd be very content with an explanation on what is done in the first code block -- the one from litb's solution.

Community
  • 1
  • 1
Rubens
  • 14,478
  • 11
  • 63
  • 92
  • Why do you need to derive your own solution? Use litb's code as a library. Do you want a more manageable version of it? I encapsulated it better at one point. But I think he actually has a self-contained version towards the end, if memory serves. – Potatoswatter Feb 27 '13 at 11:14
  • @Potatoswatter The problem here is that I don't even *know* if the solution works on my problem. I haven't been able to actually use it with my code -- mostly because I haven't actually understood what's been done there, in libt's solution. – Rubens Feb 27 '13 at 11:21

4 Answers4

7

The blog post and its code is unfortunately a bit unclear. The concept is simple: an explicit template instantiation gets a free backstage pass to any class, because

  • An explicit instantiation of a library class may be an implementation detail of a client class, and
  • Explicit instantiations may only be declared at namespace scope.

The natural way to distribute this backstage pass is as a pointer to member. If you have a pointer to a given class member, you can access it in any object of that class regardless of the access qualification. Fortunately, pointer-to-members can be compile-time constants even in C++03.

So, we want a class which generates a pointer to member when it's explicitly instantiated.

Explicit instantiation is just a way of defining a class. How can merely generating a class do something? There are two alternatives:

  • Define a friend function, which is not a member of the class. This is what litb does.
  • Define a static data member, which gets initialized at startup. This is my style.

I'll present my style first, then discuss its shortcoming, and then modify it to match litb's mechanism. The end result is still simpler than the code from the blog.

Simple version.

The class takes three template arguments: the type of the restricted member, its actual name, and a reference to a global variable to receive a pointer to it. The class schedules a static object to be initialized, whose constructor initializes the global.

template< typename type, type value, type & receiver >
class access_bypass {
    static struct mover {
        mover()
            { receiver = value; }
    } m;
};

template< typename type, type value, type & receiver >
typename access_bypass< type, value, receiver >::mover
    access_bypass< type, value, receiver >::m;

Usage:

type_of_private_member target::* backstage_pass;
template class access_bypass <
    type_of_private_member target::*,
    & target::member_name,
    backstage_pass
>;

target t;
t.* backstage_pass = blah;

See it work.

Unfortunately, you can't rely on results from this being available for global-object constructors in other source files before the program enters main, because there's no standard way to tell the compiler which order to initialize files in. But globals are initialized in the order they're declared, so you can just put your bypasses at the top and you'll be fine as long as static object constructors don't make function calls into other files.

Robust version.

This borrows an element from litb's code by adding a tag structure and a friend function, but it's a minor modification and I think it remains pretty clear, not terribly worse than the above.

template< typename type, type value, typename tag >
class access_bypass {
    friend type get( tag )
        { return value; }
};

Usage:

struct backstage_pass {}; // now this is a dummy structure, not an object!
type_of_private_member target::* get( backstage_pass ); // declare fn to call

// Explicitly instantiating the class generates the fn declared above.
template class access_bypass <
    type_of_private_member target::*,
    & target::member_name,
    backstage_pass
>;

target t;
t.* get( backstage_pass() ) = blah;

See it work.

The main difference between this robust version and litb's blog post is that I've collected all the parameters into one place and made the tag structure empty. It's just a cleaner interface to the same mechanism. But you do have to declare the get function, which the blog code does automatically.

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • unfortunately you cannot just put the bypass things at the top of the TU. since the static datamember is an instantiated entity, the ordering rule does not apply. – Johannes Schaub - litb Feb 27 '13 at 19:07
4

OK, so you asked about how to make that weird "Rob" code work with your use case, so here it is.

// the magic robber
template<typename Tag, typename Tag::type M>
struct Rob {
    friend typename Tag::type get(Tag) {
        return M;
    }
};

// the class you can't modify
class A {
private:
    std::map<int, int> A_map;
};

struct A_f {
    typedef std::map<int, int> A::*type;
    friend type get(A_f);
};

template struct Rob<A_f, &A::A_map>;

class B : private A {
public:
    inline void uncover() {
        std::map<int, int>::iterator it = (this->*get(A_f())).begin();
    }
};

Now, I personally think the cure here may be worse than the disease, despite that I'm usually the last one you'll see claiming that abusing C++ is OK. You can decide for yourself, so I've posted this as a separate answer from my one using the preprocessor to do it the old-school way.

Edit:

How It Works

Here I will replicate the code above, but with the types simplified and the code drawn out more, with copious comments. Mind you, I did not understand the code very well before I went through this exercise, I don't understand it completely now, and I certainly won't remember how it works tomorrow. Caveat maintainer.

Here's the code we aren't allowed to change, with the private member:

// we can use any type of value, but int is simple
typedef int value_type;

// this structure holds value securely.  we think.
struct FortKnox {
    FortKnox() : value(0) {}
private:
    value_type value;
};

Now for the heist:

// define a type which is a pointer to the member we want to steal
typedef value_type FortKnox::* stolen_mem_ptr;

// this guy is sort of dumb, but he knows a backdoor in the standard
template<typename AccompliceTag, stolen_mem_ptr MemPtr>
struct Robber {
    friend stolen_mem_ptr steal(AccompliceTag) {
        return MemPtr; // the only reason we exist: to expose the goods
    }
};

// this guy doesn't know how to get the value, but he has a friend who does
struct Accomplice {
    friend stolen_mem_ptr steal(Accomplice);
};

// explicit instantiation ignores private access specifier on value
// we cannot create an object of this type, because the value is inaccessible
// but we can, thanks to the C++ standard, use this value in this specific way
template struct Robber<Accomplice, &FortKnox::value>;

// here we create something based on secure principles, but which is not secure
class FortKnoxFacade : private FortKnox {
public:
    value_type get_value() const {
        // prepare to steal the value
        // this theft can only be perpetrated by using an accomplice
        stolen_mem_ptr accessor = steal(Accomplice()); // it's over now
        // dereference the pointer-to-member, using this as the target
        return this->*accessor;
    }
};

int main() {
    FortKnoxFacade fort;
    return fort.get_value();
}
John Zwinck
  • 239,568
  • 38
  • 324
  • 436
  • +1 It works! \o/ Thanks very much! Would you mind to do a shallow step-by-step, just as an explanation of what is done here. I don't really get all that `typename Tag::type M` and `friend type get(A_F)` thing. – Rubens Feb 27 '13 at 11:59
  • 1
    I've edited my answer to add quite a bit more detail. I do this with quite some reservation, however, because I think this entire approach will cause whoever inherits your code to curse your name--perhaps even more coarsely than you must be cursing the library writer who put you in this mess. – John Zwinck Feb 27 '13 at 12:48
  • @Rubens and John, see my answer for a simpler style of doing the same thing, which I worked out some time ago. Please let me know if you want a more in-depth description, but it should be easier to wrap your head around than the original, which does have rather confusing data flow. – Potatoswatter Feb 27 '13 at 13:36
  • @potatoswatter whether it is simplier or cleaner is in the eye of the beholder :-) you version can be easier explained, since it doesn't need to cover ADL. but the type of the member has to be mentioned twice. It is not as simple as just deriving the tag type from a base class (the silly gcc warning you then get can be worked around). that said, i do like your explanation indeed. – Johannes Schaub - litb Feb 27 '13 at 19:19
  • @JohnZwinck and everybody, Thank you all for the patience and very helpful explanation. Wish I could accept all the answers, but I can't, so I picked one up with my pseudo randomization. Thanks again! (: – Rubens Feb 27 '13 at 22:16
  • Why do we need "Accomplice" in the function steal? I tried to delete the parameter Accomplice in the function steal, but the code do not compile. It complains that "steal was not declared in this scope". – Casualet Jan 14 '17 at 13:19
3

How about something more brutal?

// MY CODE -- module "b.h"
# define private protected
# include <a.h>
# undef private
class B : private A {
    // now you can access "private" members and methods in A
John Zwinck
  • 239,568
  • 38
  • 324
  • 436
  • 3
    That's what we call undefined behavior. – Potatoswatter Feb 27 '13 at 11:13
  • 1
    @Rubens, it has to. Preprocessor substitutes each and every `private` with `protected` - it has no `reserved words` concept. After that, your c++ compiler sees `protected` where `private` was suppose to be. Then it will allow you to access all the **private** methods/variables from `a.h`. Heh – Aniket Inge Feb 27 '13 at 11:15
  • Ah, `private` becomes `protected`, then the library is included, and all the code in the library uses the *redefinition* of private. Amazingly scary, bro' (: +1 for creativity ^^ – Rubens Feb 27 '13 at 11:16
  • 1
    @Rubens: yeah, it pretty much does work. I've never encountered a system on which it would fail, and I doubt the OP will either. That said, Potatoswatter is right that it's not totally kosher. But nothing about this question is! – John Zwinck Feb 27 '13 at 11:16
  • Couldn't this affect code which relies upon [`SFINAE`](http://en.wikipedia.org/wiki/Substitution_failure_is_not_an_error). – Peter Wood Feb 27 '13 at 11:21
  • @PeterWood: probably yes. But does the lame-o code that the OP is forced to cope with rely on SFINAE? I wouldn't bet on it! – John Zwinck Feb 27 '13 at 11:22
  • @JohnZwinck The library I'm using I'm quite sure that does not use `SFINAE` aspects, but mine does, as I have a serialization process mangled in the code, and it uses a lot of `std::enable_if<>` statements. How would simply changing `private` to `protected`/`public` cause these statements to fail? – Rubens Feb 27 '13 at 11:30
  • 2
    it can rearrange data, because consecutive data members with the same accessibility are required to be at increasing addresses – Cheers and hth. - Alf Feb 27 '13 at 11:34
  • @Rubens: I doubt it will cause anything to fail, and if it messes up your enable_if, it will probably show up during compilation. But if you're very afraid of undefined behavior, you can try the other solution I posted, and decide for yourself which is more distasteful. – John Zwinck Feb 27 '13 at 11:43
  • 1
    Specifically, if the `#define` is in effect while a library header is included, it's UB, and if it's in effect for a given header in one TU but not another, then it will likely cause violation of the one-definition rule. And yes, it can cause the data structure to be rearranged. – Potatoswatter Feb 27 '13 at 11:46
  • @Aniket: It doesnt have to, it is UB. Although it sadly will in a lot of implementations. – PlasmaHH Feb 27 '13 at 11:49
  • Again I totally agree that it *could* cause bad things to happen. But probably it will work fine. Does anybody like my other answer better? – John Zwinck Feb 27 '13 at 11:50
  • 2
    If the header `` defines a class like `class foo { int bar; };` ---without the `private` keyword--- then `bar` remains private. To solve it also `#define class struct` – comocomocomocomo Feb 27 '13 at 11:51
  • I think the main danger is that a compiler (perhaps a future version of whatever you're using) would diagnose the ODR violation, even if you never run on an ABI where access qualification affects data layout. UB doesn't just mean crashing, in means anything can go wrong. – Potatoswatter Feb 27 '13 at 13:44
1

The best-packaged version I know of this idiom is as follows:

template<class Tag,typename Tag::type MemberPtr>
struct access_cast{
 friend typename Tag::type get(Tag){return MemberPtr;};
};

template<class Tag,class MemberPtr>
struct access_tag{
 typedef MemberPtr type;
 friend type get(Tag);
};

class A {
public:
 auto x() const {return x_;};
private: 
 int x_ = 9;
};

#include <iostream>

struct AMemTag: access_tag<AMemTag,int A::*>{}; //declare tag
template struct access_cast<AMemTag,&A::x_>; //define friend get function

int main() {
 A a;
 std::cout<<a.x()<<"\n";
 a.*get(AMemTag()) = 4; //dereference returned member pointer and modify value
 std::cout<<a.x()<<"\n";
}

See it work.

Chris Chiasson
  • 547
  • 8
  • 17