Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

boost::bind with protected members & context

Tags:

c++

In the below code, there are two "equivalent" calls to std::for_each using boost:bind expressions. The indicated line compiles, the indicated failing line fails. The best explanation I can find in the standard amounts to "because we said so". I'm looking for "why the standard indicates this behavior". My suppositions are below.

My question is simply: Why does the indicated line compile and the equivalent following line fail to compile (and I don't want because "the standard says so", I already know that - I will not accept any answers that give this as an explanation; I'd like an explanation as to why the standard says so).

Notes: Although I use boost, boost is irrelevant to this question, and the error in various formats has been reproduced using g++ 4.1.* and VC7.1.

#include <boost/bind.hpp>
#include <iostream>
#include <map>
#include <algorithm>

class Base
{
protected:
        void foo(int i)
        { std::cout << "Base: " << i << std::endl; }
};

struct Derived : public Base
{
        Derived()
        {
                data[0] = 5;
                data[1] = 6;
                data[2] = 7;
        }

        void test()
        {
                // Compiles
                std::for_each(data.begin(), data.end(),
                        boost::bind(&Derived::foo, this,
                                boost::bind(&std::map<int, int>::value_type::second, _1)));

                // Fails to compile - why?
                std::for_each(data.begin(), data.end(),
                        boost::bind(&Base::foo, this,
                                boost::bind(&std::map<int, int>::value_type::second, _1)));
        }

        std::map<int, int> data;
};

int main(int, const char**)
{
        Derived().test();

        return 0;
}

The indicated line fails with this error: main.C: In member function 'void Derived::test()': main.C:9: error: 'void Base::foo(int)' is protected main.C:31: error: within this context

As noted, the supposedly equivalent statement above compiles cleanly (and if the offending statement is commented out, runs with the expected result of printing “5”, “6”, “7” on separate lines).

While searching for an explanation, I came across 11.5.1 in the standard (specifically, I’m looking at the 2006-11-06 draft):

An additional access check beyond those described earlier in clause 11 is applied when a non-static data member or nonstatic member function is a protected member of its naming class (11.2)105) As described earlier, access to a protected member is granted because the reference occurs in a friend or member of some class C. If the access is to form a pointer to member (5.3.1), the nested-name-specifier shall name C or a class derived from C. All other accesses involve a (possibly implicit) object expression (5.2.5). In this case, the class of the object expression shall be C or a class derived from C.

After reading this, it became evidently why the second statement failed while the first succeeded, but then the question came up: What is the rationale for this?

My initial thought was that the compiler was expanding the boost::bind templates, discovering that Base::foo was protected and kicking it out because boost::bind<…> was not a friend. But, the more I thought about this explanation, the less it made sense, because if I recall correctly, as soon as you take the pointer to a member (assuming you initially are within access control of the member), all access control information is lost (i.e. I could define a function that returns an arbitrary pointer to a member that alternately returns a public, protected or private member depending on some input and the returner would be none the wiser).

More I thought about it, and the only plausible explanation I could come up with why it should make a difference was in the case of multiple inheritance. Specifically, that depending on the class layout, the member pointer when calculated from Base would be different than that calculated from Derived.

like image 271
Nathan Ernst Avatar asked Jun 16 '10 23:06

Nathan Ernst


2 Answers

It's all about "context". In the first call the context of the call is Derived which has access to the protected members of Base and hence is allowed to take addresses of them. In the second the context is "outside of" Derived and hence outside of Base so the protected member access is not allowed.

like image 179
GrafikRobot Avatar answered Nov 07 '22 01:11

GrafikRobot


Actually, this seems logical. Inheritance gives you access to Derived::foo and not to Base::foo. Let me illustrate with a code example:

struct Derived : public Base
{
    void callPrivateMethod(Base &b)
    {
        // this should obviously fail
        b.foo(5);

        // pointer-to-member call should also fail
        void (Base::*pBaseFoo) (int) = &Base::foo; // the same error as yours here
        (b.*pBaseFoo)(5);
    }
};
like image 44
itotsev Avatar answered Nov 07 '22 02:11

itotsev