nonoitall
nonoitall

Reputation: 1282

Is it okay to cast a pointer-to-member-variable in this instance?

I've been refreshing/updating my knowledge of C++ lately, and learning about strict aliasing has made me a bit wary of casting pointers of one type to another. I know that this following code sample works in practice on my compiler, but I want to make sure that it conforms to current standards:

#include <iostream>

using namespace std;

class MyBase {

    public:
    virtual void DoSomething() = 0;

};

class MyDerived1 : public MyBase {

    public:
    virtual void DoSomething() {
        cout << "I'm #1" << endl;
    }

};

class MyDerived2 : public MyBase {

    public:
    virtual void DoSomething() {
        cout << "I'm #2" << endl;
    }

};

template <typename Base, typename Member1, typename Member2>
struct Tuple {

    public:
    Base* Get(int i) {
        return &(this->*(lookupTable[i]));
    }

    private:
    Member1 member1;
    Member2 member2;

    static Base Tuple::* const lookupTable[2];

};

template <typename Base, typename Member1, typename Member2>
Base Tuple<Base, Member1, Member2>::* const Tuple<Base, Member1, Member2>::lookupTable[2] = {
    reinterpret_cast<Base Tuple<Base, Member1, Member2>::*>(&Tuple::member1),
    reinterpret_cast<Base Tuple<Base, Member1, Member2>::*>(&Tuple::member2)
};

int main() {

    Tuple<MyBase, MyDerived1, MyDerived2> tuple;

    tuple.Get(0)->DoSomething();
    tuple.Get(1)->DoSomething();

    return 0;

}

Essentially, this simple tuple contains a pair of elements, each of which should derive from a common base class. The Get function returns a Base* to the member that the given index represents.

The key part that I'm wondering about is the reinterpret_casts. I know that casting from Derived Struct::* to Base Struct::* is generally a no-no, but in this case I only use the pointers-to-member-variable to get a pointer to the object. (I don't try to copy a derived object as though it were a base object, nor stuff a base object into a derived object's memory.) This works as intended on G++, and I just want to be sure that I'm not going to get bitten by any compliant compilers for doing this.

Upvotes: 1

Views: 2479

Answers (4)

9dan
9dan

Reputation: 4272

C style cast is always better than reinterpret_cast.

If C style cast work, it is valid platform independently.

Always avoid reinterpret_cast.

EDITED

I mean, with reinterpret_cast you could point wrong memory address, C style cast handle all the platform related issues such as ABI, memory align, pointer size, etc.

EDITED By the inspiration of commentators, I've read ISO/IEC 14882:2003 section 5.2.10 "Reinterpret_cast".

Of course my comprehension is limited, but it strikes me to remember why I hated reinterpret_cast in the first place.

I think, reinterpret_cast is lack of or has very limited awareness of inheritance hierarchy.

If cast operand is a instance pointer of class which has complicated inheritance hierarchy (such as ATL/COM classes), one reinterpret_cast is enough to kill you process with incomprehensible errors.

We can use C style cast with vague knowledge of actual cast operation behind. But we must really know exact detail to use reinterpret_cast safely.

Upvotes: -1

aschepler
aschepler

Reputation: 72271

Use of reinterpret_cast is almost never portable. On top of that, the only valid use of pointer to member casts are the implicit cast from Type Derived::* to Type Base::* and careful uses of the static_cast from Type Base::* to Type Derived::*. Since you want to change the type of the member, not the type of the object containing members, this is neither of those.

How about putting tiny functions in that array instead of pointers to members? The following code is tested and should be entirely portable.

#include <iostream>

using namespace std;

class MyBase {

    public:
    virtual void DoSomething() = 0;

};

class MyDerived1 : public MyBase {

    public:
    virtual void DoSomething() {
        cout << "I'm #1" << endl;
    }

};

class MyDerived2 : public MyBase {

    public:
    virtual void DoSomething() {
        cout << "I'm #2" << endl;
    }

};

template <typename Base, typename Member1, typename Member2>
struct Tuple {

    public:
    Base* Get(int i) {
        return &(this->*lookupTable[i])();
    }

    private:
    Member1 member1;
    Member2 member2;

    template <typename MemType, MemType Tuple::*member>
    Base& GetMember() { return this->*member; }

    typedef Base& (Tuple::*get_member_func)();
    static const get_member_func lookupTable[2];

};

template <typename Base, typename Member1, typename Member2>
const typename Tuple<Base, Member1, Member2>::get_member_func
Tuple<Base, Member1, Member2>::lookupTable[2] = {
    &Tuple::GetMember<Member1, &Tuple::member1>,
    &Tuple::GetMember<Member2, &Tuple::member2>
};

int main() {

    Tuple<MyBase, MyDerived1, MyDerived2> tuple;

    tuple.Get(0)->DoSomething();
    tuple.Get(1)->DoSomething();

    return 0;

}

Upvotes: 1

Mark B
Mark B

Reputation: 96233

EDIT: Reference from standard. If I'm reading it right, as you don't meet either of the exceptions, what you've done is unspecified and so may or may not work on any particular compiler. There aren't any exceptions for the type of the member being related.

From 5.2.10/9 (reinterpret_cast):

An rvalue of type “pointer to member of X of type T1” can be explicitly converted to an rvalue of type “pointer to member of Y of type T2” if T1 and T2 are both function types or both object types.66) The null member pointer value (4.11) is converted to the null member pointer value of the destination type. The result of this conversion is unspecified, except in the following cases:

— converting an rvalue of type “pointer to member function” to a different pointer to member function type and back to its original type yields the original pointer to member value.

— converting an rvalue of type “pointer to data member of X of type T1” to the type “pointer to data member of Y of type T2” (where the alignment requirements of T2 are no stricter than those of T1) and back to its original type yields the original pointer to member value.

Upvotes: 0

&#214;&#246; Tiib
&#214;&#246; Tiib

Reputation: 10979

You should not use reinterpret_cast there. Actually you should not mention usage of reinterpret_cast anywhere where your goal is portability. reinterpret_cast is by definition something that has platform-specific results.

For casting a pointer to base into pointer of derived class use dynamic_cast, it will return NULL when the object pointed is not of derived class. If you are absolutely sure that the class is correct then you may use static_cast.

Upvotes: 2

Related Questions