views:

212

answers:

3

Hello,

I need a way to get a pointer to the start of an object in C++. This object is used inside a template so it can be any type (polymorphic or not) and could potentially be an object that uses multiple inheritance.

I found this article which describes a way to do it (see the section called "Dynamic Casts") using typeid and a dynamic_cast to void* in the case that T is a polymorphic type.

This works perfectly well on MSVC, however on GCC (4.x) it seems to fall on its arse and spits out a compiler error when it is used with a non-polymorphic type.

Does anyone know a way to:

  • Make GCC behave itself, and evaluate typeid correctly
  • Or another way to do this, that will compile on GCC

Below is the code I am currently using to try and achieve this.

template <typename T>
void* dynamicCastToVoidPtr(T *const ptr)
{
    // This is done using a separate function to avoid a compiler error on some 
    // compilers about non-polymorphic types when calling startOfObject
    return dynamic_cast<void*>(ptr);
}

template <typename T>
void* startOfObject(T *const ptr)
{
    // In cases of multiple inheritance, a pointer may point to an offset within 
    // another object
    // This code uses a dynamic_cast to a void* to ensure that the pointer value 
    // is the start of an object and not some offset within an object
    void *start = static_cast<void*>(ptr);
    if(start)
        typeid(start = dynamicCastToVoidPtr(ptr), *ptr);
    return start;
}

template <typename T>
void doSomethingWithInstance(T *const instance)
{
    // Here is where I need to get a void* to the start of the object
    // You can think of this as the deleteInstance function of my memory pool
    // where the void* passed into freeMemory should point to the
    // start of the memory that the memory pool returned previously
    void *start = startOfObject(instance);
    if(start)
        allocator->freeMemory(start);
}

Thanks.

A: 

I did some investigation, and you may have found a bug in GCC here; I would report it. However, there may be some rule I can't find that says that the template function on the left side of the comma operator does need to be instantiated in this case, even though the overall expression is not evaluated; in that case the technique in the article is just wrong.

I suggest you see if boost::type_traits::is_polymorphic can be made to work for you.

Zack
Thanks for the suggestion, however this code is part of a library and I am trying to avoid a required dependency on Boost.
MrD.
You could just read boost/type_traits/is_polymorphic.hpp and figure out what it's doing and do it yourself :) (Good luck with that, though, Boost is many things but comprehensible ain't one of them.)
Zack
@Zack Yeah, I'll probably have a look at that. If I could tell if a type was polymorphic at compile time then I could use template specialisation to solve the problem instead.
MrD.
There is an extractor tool on boostpro which cleans up the boost headers (removes anything not necessary for your compiler).
Matthieu M.
+1  A: 

The exact error message by gcc is

error: cannot dynamic_cast &n (of type struct N*) to type void* (source type is not polymorphic)

This could be handled by using boost::is_polymorphic in combination with boost::enable_if and boost::disable_if, unfortunately gcc chokes with the obvious approach, so here is the workaround:

template <class T>
void* address_of_impl(T* p, boost::enable_if< boost::is_polymorphic<T>, int >)
{
  return dynamic_cast<void*>(p);
}

template <class T>
void* address_of_impl(T* p, ...) { return static_cast<void*>(p); }

template <class T>
void* address_of(T* p) { return address_of_impl(p, 0); }

Where we use SFINAE at our advantage (the ellipsis is always considered last in overload resolution so the compiler first attempts to use the dynamic_cast version which fails for non polymorphic types because of enable_if).

I've test it on gcc 3.4 and it passed. I'm investigating in another question why using disable_if instead of ... doesn't work.

EDIT:

and it was a simple typo (forgot the ::type bit):

template <class T>
typename boost::enable_if< boost::is_polymorphic<T>, void* >::type
address_of(T* p) { return dynamic_cast<void*>(p); }

template <class T>
typename boost::disable_if< boost::is_polymorphic<T>, void* >::type
address_of(T* p) { return static_cast<void*>(p); }
Matthieu M.
A: 

I've found a solution from another question that lets me work out at compile time if a type is polymorphic and I can then use this with template specialisation to use the correct type of cast. Apparently this method might break if the compiler adds padding between sub-objects, but I can hopefully add some compile time asserts on some known cases to catch that. It does compile and run correctly on both MSVC and GCC.

This is the code to work out if a type is polymorphic.

#define SIMPLE_POLYMORPHIC(TYPE, POLYMORPHIC)   \
    template <>                                 \
    struct IsPolymorphic<TYPE>                  \
    {                                           \
        static const bool value = POLYMORPHIC;  \
    };

template <typename T>
struct IsPolymorphic
{
    struct Derived : public T { virtual ~Derived(); };
    static const bool value = (sizeof(Derived) == sizeof(T));
};

SIMPLE_POLYMORPHIC(int, false);
SIMPLE_POLYMORPHIC(unsigned int, false);
// ... do this for all intrinsic or non-derivable types

The code to perform the casting based on whether the type is polymorphic.

template <typename T, bool isPolymorphic = IsPolymorphic<T>::value>
struct StartOfObject
{
    static void* getStart(T *const ptr)
    {
        return static_cast<void*>(ptr);
    }
};

template <typename T>
struct StartOfObject<T, true>
{
    static void* getStart(T *const ptr)
    {
        if(ptr)
            return dynamic_cast<void*>(ptr);
        return NULL;
    }
};

And a test case for it.

#define CLASS_STUFF(CLASS)      \
    public:                     \
        CLASS() {}              \
        virtual ~CLASS() {}     \
        int m_##CLASS;

class A
{
    CLASS_STUFF(A);
};

class B : public A
{
    CLASS_STUFF(B);
};

class C
{
};

#include <iostream>

int main()
{
    std::cout << IsPolymorphic<A>::value << std::endl;
    std::cout << IsPolymorphic<B>::value << std::endl;
    std::cout << IsPolymorphic<C>::value << std::endl;
    std::cout << IsPolymorphic<int>::value << std::endl;

    StartOfObject<A>::getStart(new A());
    StartOfObject<B>::getStart(new B());
    StartOfObject<C>::getStart(new C());
    StartOfObject<int>::getStart(new int());

    return 0;
};
MrD.