3

I would like to detect if a particular type has a member: directly and not as a result of inheritance.

The purpose is to determine if a specific type 'has a trait' such as the ability to serialize. Naturally, and for extending the example, a sub-type might not have the ability to serialize even if the parent type does.

  • Is there (and if so) a "standard" solution to this request?
  • Is there a flaw (excluding the limitations it imposes) with the non-convertible pointer approach presented at the bottom?

When using is_member_function_pointer or other detection mechanisms, inheritance is in play. Note that the output is "1" even though B does not define the member.

#include <type_traits>
#include <iostream>

struct A {
   void member() { }
};

struct B : A {
};

int main()
{
    std::cout << "B has member? "
              << std::is_member_function_pointer<decltype(&B::member)>::value
              << std::endl; 
}

The closest I have been able to achieve is when using a non-convertible pointer (B** has no implicit conversion to A**), although such is a bit awkward to work with. It also imposes the additional argument matched to the type and prevents any direct inheritance.

#include <type_traits>
#include <iostream>

struct A {
    // would match std::declval<B*> as B* -> A*,
    // hence forcing failure through B** -> A**.
    // void member(A*) { }
    void member(A**) { }
};

struct B : A {
    // succeeds compilation aka "found" if not commented
    // void member(B**) { }
};

int main()
{
    // This actually fails to compile, which is OKAY because it
    // WORKS when used with SFINAE during the actual detection.
    // This is just a simple example to run.
    //   error: invalid conversion from 'B**' to 'A**'
    std::cout << "B has member? "
              << std::is_member_function_pointer<
                decltype(std::declval<B>().member(std::declval<B**>()))
              >::value
              << std::endl; 
}
user2864740
  • 60,010
  • 15
  • 145
  • 220
  • 1
    Does this answer your question? [Execute function inside function template only for those types that have the function defined](https://stackoverflow.com/questions/60112341/execute-function-inside-function-template-only-for-those-types-that-have-the-fun) – NutCracker Feb 11 '20 at 22:48
  • Instead of trying to detect the presence of a member, you could require the thing passed to the function specialize some type trait you create for you library and then you can check the result of the type trait. – NathanOliver Feb 11 '20 at 22:50
  • @NutCracker No, it doesn't - unless the details are buried. That answer/question does not deal with *inheritance*, which is where this issue is arising. – user2864740 Feb 11 '20 at 22:50
  • @NathanOliver That's done too for the 'unobtrusive form' :) I'm hoping to get a similar effect for the 'obtrusive/inline' form. – user2864740 Feb 11 '20 at 22:52
  • If you compile with a recent [GCC](http://gcc.gnu.org/) you could write some [GCC plugin](https://gcc.gnu.org/onlinedocs/gccint/Plugins.html) for your needs (provide some new GCC builtin doing what you want) – Basile Starynkevitch Feb 11 '20 at 22:52
  • @BasileStarynkevitch Unfortunately there are several less common compilers that must be supported (can mandate full C++14 support though). – user2864740 Feb 11 '20 at 22:53
  • And if you use [Clang](http://clang.llvm.org/) you could extend it too – Basile Starynkevitch Feb 11 '20 at 22:54
  • 1
    @BasileStarynkevitch So now I just need to write, install, and maintain plugin for Clang, GCC, IIC, NVCC, etc ;-) – user2864740 Feb 11 '20 at 22:56
  • You could decide to choose to use mostly GCC. – Basile Starynkevitch Feb 13 '20 at 09:46

3 Answers3

3

There's a neat trick that can help with that.

The type of &B::member is actually void (A::*)(), not void (B::*)() (if member is inherited).

Use SFINAE to check that &B::member exists and has a proper type:

template <typename T, typename = void>
struct has_member : std::false_type {};

template <typename T> struct has_member
    <T, std::enable_if_t<std::is_same_v<void (T::*)(), decltype(&T::member)>>>
    : std::true_type
{};

This only works for one specific member type (member has to be void member()). Generalizing it for any type is left as an exercise to the reader.


Or you can get fancy and use the fact that void (B::*)() is for some reason not implicitly convertible to void (A::*)() specifically when passing a template parameter:

template <typename T, T>
struct detect_member_helper {};

template <typename T>
using detect_member = detect_member_helper<void (T::*)(), &T::member>;

template <typename T>
inline constexpr bool has_member = std::experimental::is_detected_v<detect_member, T>;
HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
  • It doesn't really work. Check [this](https://godbolt.org/z/Vyh-Lr) example – NutCracker Feb 11 '20 at 23:14
  • @NutCracker That's because it expects a different signature of `member`: https://godbolt.org/z/MG5C_S – HolyBlackCat Feb 11 '20 at 23:15
  • 1
    @HolyBlackCat Wow, that.. works! I couldn't figure out how to work in `is_same_v` yesterday. It was also very hand to note "the type of &B::member is actually void (A::*)()". A few small shims and works in Clang 3.9.1 (yeah, old, it's one of our .. baseline .. targets). – user2864740 Feb 11 '20 at 23:29
2

This might work for you:

#include <type_traits>
#include <iostream>

struct A {
    void member(A**)
    {}
};

struct B : A
{};

template <typename, typename = void>
struct hasMember : std::false_type {};

template <typename T>
struct hasMember<T, std::void_t<decltype(std::declval<T*>()->member())>>
    : std::is_same<decltype(std::declval<T*>()->member()), void>
{};

int main() {
    std::cout << "B has member ? "
              << hasMember<B>::value
              << std::endl; 
}

And the output is going to be B has member ? 0.

Check it out live

NutCracker
  • 11,485
  • 4
  • 44
  • 68
  • [Pro Tip] `((T*)nullptr)` can be replaced with `std::declval()` – NathanOliver Feb 11 '20 at 22:56
  • Thanks. That's effectively how the code in the original code is being used (the SFINAE/detector was eliminated for example). However, it still relies on the non-convertible pointer / non-overridden method to function. It's workable here.. just hoping it might be possible to elicit without needing such. – user2864740 Feb 11 '20 at 22:57
2

Using the fact that &B::member is void (A::*)(), you might do

template <typename C, typename Sig>
struct classMember : std::false_type{};

template <typename C, typename Ret, typename ... Ts>
struct classMember<C, Ret (C::*)(Ts...)> : std::true_type{};
// and other specialization for cv and ref and c ellipsis

template <typename, typename = void>
struct hasMember : std::false_type {};

template <typename T>
struct hasMember<T, std::enable_if_t<classMember<T, decltype(&T::member)>::value>> : std::true_type
{};

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302