This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

dynamic_cast in SFINAE



Hello

This is a follow-up question to https://e2e.ti.com/support/development_tools/compiler/f/343/t/387340 , or at least it's closely related.

Let me first briefly explain what I was trying to achieve: I have a custom pool allocator. The pool allocator essentially manages a number of fixed-size blocks, identified by a void pointer (to the beginning of the block). Objects are then placement-constructed into these memory buffers. Finally, I'd like to properly release the objects to their pool, which is working well except if the pointer is to a base-object of the allocated object, in which case I cannot reinterpret_cast to void* as that would not yield the pointer to the memory block, i.e.

struct A { int x; };
struct B { int y; };
struct C : A, B {};

void *allocate(size_t nbytes);
void deallocate(void* block);

B* ptr = new (allocate(sizeof(C))) C(); // implicit conversion to B*
ptr->~B(); // ouch, non-virtual... however, C* ptr and ptr->~C() would work well here
deallocate( static_cast<void*>(ptr) ); // ouch, does not (necessarily) point to the most-derived object

That is fine, as it matches the usual new/delete semantics, except if we add a virtual destructor to B, in which case delete would correctly free all memory. At the same time, I could simply use dynamic_cast to obtain a pointer to the most-derived object:

struct A { int x; };
struct B { int y; virtual ~B() {} };
struct C : A, B {};

void *allocate(size_t nbytes);
void deallocate(void* block);

B* ptr = new (allocate(sizeof(C))) C(); // implicit conversion to B*
ptr->~B(); // clean up
deallocate( dynamic_cast<void*>(ptr) ); // all is well

The problem I face arises when trying to wrap the memory management in some generic Ptr<T> class. A dynamic_cast<cv void*>(ptr) is only allowed if the static type of ptr is polymorphic. Otherwise I would assume that the pointer is to the most-derived object and would simply use a static_cast to void*. To solve that problem I devised a little SFINAE helper that would tell me if a type T is polymorphic (it would not tell me if T's destructor is virtual, but I don't care, I only want to know if I can dynamic_cast). The code looks as follows:

template<typename T>
struct IsPolymorphic
{
	typedef char (&t)[1];
	typedef char (&f)[2];
	
	template<typename U>
	static U* make();
	
	template<typename U>
	static t test(U* ptr, char (*sfinae)[ sizeof(dynamic_cast<const volatile void*>(make<U>())) ] = 0);
	static f test(...);

	static const int value = (sizeof( test(make<T>()) ) == 1);
};

template<typename T>
const int IsPolymorphic<T>::value;

struct NonPolymorphic
{
	int mIAmASimpleStruct;
};

struct Polymorphic
{
	int mIHaveAVirtualTable;
	virtual ~Polymorphic() {}
};

int main()
{
	typedef char static_assert_1[ IsPolymorphic<NonPolymorphic>::value ? -1 : 1 ];
	typedef char static_assert_2[ IsPolymorphic<Polymorphic>::value ? 1 : -1 ];
}

[Note: this is very similar to the code in the question that I linked to above]

I used three different compilers to compile this code: GCC 5.3, TI's C6000 CGT 7.4.16, and TI's C6000 CGT 8.1.0. The results were:

GCC 5.3 compiles the code without complaints.

CGT 7.4.16 emits the following error:

> cl6x -mv64+ --rtti test.cpp

"test.cpp", line 11: error: the operand of a runtime dynamic_cast must have a polymorphic class type
          detected during:
            instantiation of "IsPolymorphic<T>::test [with T=NonPolymorphic]" based on template argument <NonPolymorphic> at line 14
            instantiation of class "IsPolymorphic<T> [with T=NonPolymorphic]" at line 33
"test.cpp", line 33: error: the size of an array must be greater than zero
2 errors detected in the compilation of "test.cpp".

>> Compilation failure

CGT 8.1.0 does not complain about the dynamic_cast, but seems to set IsPolymorphic<T>::value == 0 in all cases:

cl6x.exe -mv64+ --rtti test.cpp
"test.cpp", line 34: error: the size of an array must be greater than zero
1 error detected in the compilation of "test.cpp".

>> Compilation failure

I believe GCC is right (of course I do ;-) ), so is this a bug? Can it be fixed in the 7.4.x series?

Also, is there another way to test at compile time if T is a polymorphic type? Right now I can only think of an intrusive solution which would explicitly tag all polymorphic types; certainly a less than ideal solution.


Regards

Markus

  • To address your last question, since I'm still investigating your main question:

    • LLVM has a C++11 implementation of std::is_polymorphic that may be able to be cut down to work in C++03 with the TI compiler.
    • Boost also has an implementation of is_polymorphic in their type_traits library. I believe their implementation works specifically in C++03, but is far more verbose, as Boost tends to be.

  • I am no C++ expert, but I am guessing your thinking is correct per the language, and it's fringe functionality so it hasn't made its way to all implementations.

    cgt c6000 7.4 does not seem to have <type_traits> available.  The 8.x versions have a partial <type_traits>.  Unfortunately is_polimorphic [sic] is unimplemented.  Its absence may indicate that this will be a tough thing to pull off currently.  If one were willing to live with restrictions on the type of classes that can be used with T, some success might be had by combining some of the type traits they do offer, such as is_trivial, __is_vtlb, has_virtual_destructor, is_standard_layout, etc.

    You did specifically ask for compile time check, but if that is not a strict requirement, there are runtime options, e.g. using typeid.

  • That's interesting. Actually, has_virtual_destructor is exactly what I need, but it's harder to implement than is_polymorphic because in contrast to is_polymorphic it cannot be implemented portably in a library. As support for the DM6435 that we are using seems to be fading in the 8.x compiler series, I think I will have to stick with 7.4.x. And yes, I'm afraid I need a compile-time test, so typeid is not an option.

    I saw that LLVM used to implement is_polymorphic<T> by deriving from T and testing if adding a virtual destructor alters the derived type's size. That might be an option but it seemed to come with its own problems and apparently they at least temporarily replaced it with a test that looks very much like mine.

    I don't think there will be a quick solution (I was hoping for some undocumented compiler intrinsic or something like that :-) ) so I actually began adding tags to the classes for which I need the test. Not my favorite solution, but one that portably works for now.

    Still, if it's a bug in CGT maybe it can be fixed in some future release (and given that C++11 support slowly seems to be creeping into CGT (e.g. <type_traits>), I guess it will eventually be fixed).

    Markus
  • // C6000 7.6.0+
    struct NonPolymorphic
    {
        int mIAmASimpleStruct;
    };
    
    struct Polymorphic
    {
        int mIHaveAVirtualTable;
        virtual ~Polymorphic() {}
    };
    
    int main()
    {
        typedef char static_assert_1[ __is_polymorphic(NonPolymorphic) ? -1 : 1 ];
        typedef char static_assert_2[ __is_polymorphic(Polymorphic) ? 1 : -1 ];
    }

    This code compiles on C6000 7.6.0 and above by using internal operators provided by the compiler. The parser we use in C6000 7.4 and earlier is much less up-to-date with C++ SFINAE rules and edge cases, so I'm not entirely surprised that the tricks used in the dynamic_cast solution are causing issue.

  • Markus Moll said:
    As support for the DM6435 that we are using seems to be fading in the 8.x compiler series, I think I will have to stick with 7.4.x.

    DM6435 has a C64x+ CPU, which is fully supported by C6000 compiler versions 8.0.0 and above, and there are no plans to obsolete that support anytime soon.  Have you heard otherwise, or had a problem using 8.0.0?

    The TI compiler team tends to recommend that you stick to 7.4.x for existing projects because 8.0.0 is a new compiler and may perform differently.  You can try 8.0.0, but you'd need to verify that your application still meets your performance goals.

  • Archaeologist said:

    DM6435 has a C64x+ CPU, which is fully supported by C6000 compiler versions 8.0.0 and above, and there are no plans to obsolete that support anytime soon.  Have you heard otherwise, or had a problem using 8.0.0?

    Hm, I thought I had run into problems, but as I cannot recall what problems, I may be confusing things. Good to hear that C64x+ will remain to be supported in the 8.x compiler.

    James Nagurne said:

    This code compiles on C6000 7.6.0 and above by using internal operators provided by the compiler.

    Is 7.6 the predecessor to 8.x? I can only find 8.x, 7.4, 7.3, 6.0 and 8.0beta on the compiler download page.

    Markus

  • Markus Moll said:
    Is 7.6 the predecessor to 8.x? I can only find 8.x, 7.4, 7.3, 6.0 and 8.0beta on the compiler download page.

    The 7.6.x versions of the compiler were never made available outside of TI.  The 8.0.x versions are the first ones publicly released after 7.4.x.

    Thanks and regards,

    -George