Test whether a class is polymorphic
Asked Answered
A

5

10

We have a sub-project 'commonUtils' that has many generic code-snippets used across the parent project. One such interesting stuff i saw was :-

/*********************************************************************
If T is polymorphic, the compiler is required to evaluate the typeid 
stuff at runtime, and answer will be true.  If T is non-polymorphic, 
the compiler is required to evaluate the typeid stuff at compile time, 
whence answer will remain false
*********************************************************************/
template <class T> 
bool isPolymorphic() { 
   bool answer=false; 
   typeid(answer=true,T()); 
   return answer; 
}

I believed the comment and thought that it is quite an interesting template though it is not used across the project. I tried using it like this just for curiosity ...

class PolyBase {
public:
   virtual ~PolyBase(){}
};

class NPolyBase {
public:
   ~NPolyBase(){}
};


if (isPolymorphic<PolyBase>())
  std::cout<<"PolyBase = Polymorphic\n";
if (isPolymorphic<NPolyBase>())
  std::cout<<"NPolyBase = Also Polymorphic\n";

But none of those ever returns true. MSVC 2005 gives no warnings but Comeau warns typeid expression has no effect. Section 5.2.8 in the C++ standard does not say anything like what the comment says i.e. typeid is is evaluated at compile time for non-polymorphic types and at runtime for polymorphic types.

1) So i guess the comment is misleading/plain-wrong or since the author of this code is quite a senior C++ programmer, am i missing something?

2) OTOH, I am wondering if we can test whether a class is polymorphic(has at least one virtual function) using some technique?

3) When would one want to know if a class is polymorphic? Wild guess; to get the start-address of a class by using dynamic_cast<void*>(T) (as dynamic_cast works only on polymorphic classes).

Awaiting your opinions.

Thanks in advance,

Armagh answered 10/7, 2009 at 6:8 Comment(2)
Er, If the author is a senior C++ programmer why don't you check with him first? ... You will often learn a lot from experienced guys.Hirsh
Well, if i could i would not have asked it on stackoverflow :-)Armagh
T
9

I cannot imagine any possible way how that typeid could be used to check that type is polymorphic. It cannot even be used to assert that it is, since typeid will work on any type. Boost has an implementation here. As for why it might be necessary -- one case I know is the Boost.Serialization library. If you are saving non-polymorphic type, then you can just save it. If saving polymorphic one, you have to gets its dynamic type using typeid, and then invoke serialization method for that type (looking it up in some table).

Update: it appears I am actually wrong. Consider this variant:

template <class T> 
bool isPolymorphic() { 
    bool answer=false;
    T *t = new T();
    typeid(answer=true,*t); 
    delete t;
    return answer; 
}

This actually does work as name suggests, exactly per comment in your original code snippet. The expression inside typeid is not evaluated if it "does not designate an lvalue of polymorphic class type" (std 3.2/2). So, in the case above, if T is not polymorphic, the typeid expression is not evaluated. If T is polymorphic, then *t is indeed lvalue of polymorphic type, so entire expression has to be evaluated.

Now, your original example is still wrong :-). It used T(), not *t. And T() create rvalue (std 3.10/6). So, it still yields an expression that is not "lvalue of polymorphic class".

That's fairly interesting trick. On the other hand, its practical value is somewhat limited -- because while boost::is_polymorphic gives you a compile-time constant, this one gives you a run-time value, so you cannot instantiate different code for polymorphic and non-polymorphic types.

Ternion answered 10/7, 2009 at 7:3 Comment(2)
Yes i know the boost implementation which roughly uses the sizeof() technique. Thanks for the Serialization tidbit. I was interested in knowing if that commonUtils template is firstly correct and secondly is it worth preserving in the project.Armagh
Ah ha, an interesting trick indeed, but your quote of the 3.10/6 was enlightening, Thanks. One can choose over the templatized version when binary-size matters or if the user is not to be trusted with providing a virtual dtor in a polymorphic class. Alas i cannot upvote 2 times!Armagh
S
4

Since C++11, this is now available in the <type_traits> header as std::is_polymorphic. It can be used like this:

struct PolyBase {
  virtual ~PolyBase() {}
};

struct NPolyBase { 
  ~NPolyBase() {}
};

if (std::is_polymorphic<PolyBase>::value)
  std::cout << "PolyBase = Polymorphic\n";
if (std::is_polymorphic<NPolyBase>::value)
  std::cout << "NPolyBase = Also Polymorphic\n";

This prints just "PolyBase = Polymorphic".

Sidero answered 11/12, 2018 at 3:50 Comment(0)
C
3


class PolyBase {
public:   
    virtual ~PolyBase(){}
};

class NPolyBase {
public:
    ~NPolyBase(){}
};

template<class T>
struct IsPolymorphic
{
    struct Derived : T {
        virtual ~Derived();
    };
    enum  { value = sizeof(Derived)==sizeof(T) };
};


void ff()
{
    std::cout << IsPolymorphic<PolyBase >::value << std::endl;
    std::cout << IsPolymorphic<NPolyBase>::value << std::endl;
}

Concern answered 10/7, 2009 at 6:57 Comment(5)
I am not sure whether this is completely fool-proof. A compiler can add padding between sub-objects in which case sizeof() trick wouldn't work.Armagh
This would break when, say the Derived defines its own member variables, making it impractical.Spencerianism
@Indeera: You would not add any member variables as the struct Derived intentionally derives publicly from the class that you specify. The only caveat is if the specified class does not have a virtual dtor but some virtual funcs (in which case specified class is still polymorphic) apart from the padding issue. Boost assumes that a polymorphic class does define a virtual dtor and handles the padding using some compiler specifics i guess.Armagh
@Abhay: that every polymorphic class has a virtual destructor isn't the only caveat; still - as per your first comment, it relies on the empty base class optimisation too.Joslin
This simply doesn't work with most compilers. It will return false even if the class being tested has virtual functions, if the class being tested also has virtual bases somewhere in its hierarchy.Ergograph
A
1

One can use the facts that:

  1. dynamic_cast fails at compile time if the argument is not a polymorphic class. So that it can be used with SFINAE.
  2. dynamic_cast<void*> is a valid cast that returns the address of the complete polymorpic object.

Hence, in C++11:

#include <iostream>
#include <type_traits>

template<class T>
auto is_polymorphic2_test(T* p) -> decltype(dynamic_cast<void*>(p), std::true_type{});

template<class T>
auto is_polymorphic2_test(...) -> std::false_type;

template<class T>
using is_polymorphic2 = decltype(is_polymorphic2_test<T>(static_cast<T*>(0)));

struct A {};
struct B { virtual ~B(); };

int main() {
    std::cout << is_polymorphic2<A>::value << '\n'; // Outputs 0.
    std::cout << is_polymorphic2<B>::value << '\n'; // Outputs 1.
}
Assorted answered 30/7, 2019 at 10:3 Comment(0)
J
0

I'm a little confused here, and am hoping to get some comments on this answer explaining what I'm missing.

Surely if you want to find out whether a class is polymorphic, all you have to do is ask whether it supports dynamic_cast, isn't that right?

template<class T, class> struct is_polymorphic_impl   : false_type {};
template<class T> struct is_polymorphic_impl
    <T, decltype(dynamic_cast<void*>(declval<T*>()))> : true_type {};

template<class T> struct is_polymorphic :
    is_polymorphic_impl<remove_cv_t<T>, void*> {};

Can anyone point out a flaw in this implementation? I imagine there must be one, or must have been one at some point in the past, because the Boost documentation continues to claim that is_polymorphic "can't be portably implemented in the C++ language".

But "portably" is kind of a weasel word, right? Maybe they're just alluding to how MSVC doesn't support expression-SFINAE, or some dialects such as Embedded C++ don't support dynamic_cast. Maybe when they say "the C++ language" they mean "a lowest-common-denominator subset of the C++ language." But I have a nagging suspicion that maybe they mean what they say, and I'm the one who's missing something.

The typeid approach in the OP (as amended by a later answer to use an lvalue not an rvalue) also seems fine, but of course it's not constexpr and it requires actually constructing a T, which might be super expensive. So this dynamic_cast approach seems better... unless it doesn't work for some reason. Thoughts?

Jamarjamb answered 12/7, 2017 at 21:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.