30

I tried to make a traits to find if a method is virtual: (https://ideone.com/9pfaCZ)

// Several structs which should fail depending if T::f is virtual or not.
template <typename T> struct Dvf : T { void f() final; };
template <typename T> struct Dvo : T { void f() override; };
template <typename T> struct Dnv : T { void f() = delete; };

template <typename U>
class has_virtual_f
{
private:
    template <std::size_t N> struct helper {};
    template <typename T>
    static std::uint8_t check(helper<sizeof(Dvf<T>)>*);
    template<typename T> static std::uint16_t check(...);
public:
    static
    constexpr bool value = sizeof(check<U>(0)) == sizeof(std::uint8_t);
};

Test cases:

struct V  { virtual void f(); };
struct NV {         void f(); };
struct E  {                   };
struct F  { virtual void f() final; }; // Bonus (unspecified expected output)

static_assert( has_virtual_f< V>::value, "");
static_assert(!has_virtual_f<NV>::value, "");
static_assert(!has_virtual_f< E>::value, "");

But I got error: 'void Dvf<T>::f() [with T = NV]' marked final, but is not virtual.
If I don't use sizeof and directly Dvf<T>* in check, I don't have compilation error, but check is not discarded for "bad" type in SFINAE :( .

What is the proper way to detect if a method is virtual ?

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • 4
    Sfinae doesn't "look" inside definitions of classes, it's not in *"immediate context"* (see 14.8.2/8). – jrok Apr 07 '14 at 11:41
  • 6
    I am interested *why* you would want to know this. Normally, a client should only worry about the interface and not care whether there is virtual dispatch behind the scenes or not. – Matthieu M. Apr 07 '14 at 12:56
  • I think , You will have to look into the IL code of the dll to make out if the call is virtual or not .. – spetzz Apr 07 '14 at 15:38
  • @MatthieuM.: When answering to [explicitly-hide-a-base-function](http://stackoverflow.com/questions/22895078/explicitly-hide-a-base-function-in-c/22913931#22913931), I would expand my solution with a cleaner traits. – Jarod42 Apr 07 '14 at 15:47
  • @Jarod42 This behaviour is expected because `sizeof(Dvf)` instantiates `Dvf` whereas `Dvf*` does not. You cannot instantiate an invalid class during a SFINAE test or you'll get an error. As for a proper way, don't know if possible; trying. – iavr Apr 08 '14 at 14:47
  • @MatthieuM., I second that. Especially since in my classes the only public virtual functions I ever have are destructors. (IMAO, virtual functions should be private as a rule for similar reasons as data members.) Any public member function in any of my classes will be nonvirtual, but the bulk of the work may actually be carried out by delegation to a nonpublic virtual function. – Adam H. Peterson Apr 08 '14 at 18:49

2 Answers2

18

The code isn't perfect but it basically passes the tests (at least in all clangs available on wandbox and gcc since 7.):

#include <type_traits>

template <class T>
using void_t = void;

template <class T, T v1, T v2, class = std::integral_constant<bool, true>>
struct can_be_compaired: std::false_type { };

template <class T, T v1, T v2>
struct can_be_compaired<T, v1, v2, std::integral_constant<bool, v1 == v2>>: std::true_type { };

template <class T, class = void>
struct has_virtual_f: std::false_type { };

template <class T>
struct has_virtual_f<T, void_t<decltype(&T::f)>>{
    constexpr static auto value = !can_be_compaired<decltype(&T::f), &T::f, &T::f>::value;
};

struct V  { virtual void f() { }      };
struct NV {         void f() { }      };
struct E  {                           };
struct F  { virtual void f() final{ } }; // Bonus (unspecified expected output)

int main() {
   static_assert( has_virtual_f< V>::value, "");
   static_assert(!has_virtual_f<NV>::value, "");
   static_assert(!has_virtual_f< E>::value, "");
   static_assert( has_virtual_f< F>::value, "");
}

[live demo]


The relevant standard parts that theoretically let the trait fly: [expr.eq]/4.3, [expr.const]/4.23

W.F.
  • 13,888
  • 2
  • 34
  • 81
  • `If either is a pointer to a virtual member function, the result is unspecified.` but it doesn't say it can't be true either. Or am I reading the standard wrong? I guess if it's unspecified it could go either true or false, and it may be compiler dependent? – dau_sama Oct 01 '17 at 11:23
  • 2
    @dau_sama but: `An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine, would evaluate one of the following expressions:(...) a relational or equality operator where the result is unspecified; (...) ` – W.F. Oct 01 '17 at 11:36
  • 2
    @dau_sama which means it is unspecified if the two pointers should be equal when they point to virtual method but when the equality is tested inside a constexpr context it is well defined what should compiler do - it is not a core constant expression so it should cause substitution failure. – W.F. Oct 01 '17 at 11:40
14

There is probably no way to determine if a specific method is virtual. I say this because the Boost project researched traits for years and never produced such a traits test.

However, in C++11, or using the Boost library, you can use the is_polymorphic<> template to test a type to see if the type has virtual functions. See std::is_polymorphic<> or boost::is_polymorphic<> for reference.

jotik
  • 17,044
  • 13
  • 58
  • 123
llewelly
  • 339
  • 1
  • 4