Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Checking for existence of C++ member function, possibly protected

I'm trying to detect whether a class has a particular function (specifically shared_from_this(), which is inherited from std::enable_shared_from_this<Some Unknown Class>). To make things more complicated, I need to know whether it has this function even if it has been inherited from a distant base class or inherited using protected access.

I've looked at other questions such as this one, but the methods supplied do not work for detecting protected member functions.

The current method I am using is the following:

template <class T>
struct shared_from_this_wrapper : public T
{
  template <class U>
  static auto check( U const & t ) -> decltype( t.shared_from_this(), std::true_type() );

  static auto check( ... ) -> decltype( std::false_type() );
};

template<class T>
struct has_shared_from_this : decltype(shared_from_this_wrapper<T>::check(std::declval<shared_from_this_wrapper<T>>()))
{ };

The flaw in my current solution is that it does not work with classes declared final. So I am after a solution for testing for a member function that satisfies:

  1. Works with classes declared final
  2. Works with protected member functions
  3. Works with inheritance
  4. Does not need to know the return type of the function
  5. Compiles under gcc, clang, and MSVC 2013 (the last one potentially limiting overly fancy SFINAE)

Edit: I have a potential solution that works but requires befriending a helper class, which is also not an ideal solution but possibly a workaround for now (since it satisfies all requirements):

struct access
{
  template <class T>
  static auto shared_from_this( T const & t ) -> decltype( t.shared_from_this() );
};

template <class U>
static auto check( U const & t ) -> decltype( access::shared_from_this(t), std::true_type() );

static auto check( ... ) -> decltype( std::false_type() );

template<class T>
struct has_shared_from_this2 : decltype(check(std::declval<T>()))
{ };

struct A : std::enable_shared_from_this<A> {};
struct B : protected A { friend class access; };    

Another edit: examples of classes and what a type trait checking for the existence of something like shared_from_this should return:

struct A : std::enable_shared_from_this<A> {}; // should return true
struct B final : protected A {}; // should return true
struct C : A {}; // should return true
struct D {}; // should return false

I should mention that my final goal in detecting whether this function exists is to determine the return type of it in order to figure out the type on which std::enable_shared_from_this was templated. Inheriting from std::enable_shared_from_this<T> gives you std::shared_ptr<T> shared_from_this(), and T is ultimately what I need to figure out. This is necessary for proper serialization of types that inherit from std::enable_shared_from_this.

Edit part 3: The editing:

This is being done for the serialization library cereal and as such I have zero control over how a user wants to design their class. I would like to be able to serialize any user type that derives from std::enable_shared_from_this, which includes users that declare their classes as either final or use protected inheritance somewhere along the way. Any solution that requires meddling with the actual type that is checked is not a valid solution.

like image 285
Azoth Avatar asked Mar 01 '14 07:03

Azoth


1 Answers

I've put some thoughts on how to implement the things you requested and came to a totally different conclusion.

The problem at hand is very interesting: How do I check whether a class implements a hidden interface. Unfortunately the problem is a contradiction to the liskov substitution principle; one of the core object oriented principles.

This is partly due to the type structure of std::shared_ptr. shared_ptr does not reflect the inheritance relationship of its argument types. Given a class T and a class U, where class T : public U {}; holds shared_ptr<T> : public shared_ptr<U> {}; does not!

Your implementation has one fundamental flaw at the interface level. If you are looking at compile time whether a function exists and then extract the type you will only be able deserialize data structures that are use shared pointers.

Furthermore if std::shared_ptr gets deprecated or you want to use some other means to aquire memory (std::allocator interface? some region/pool allocation) you'll have to adapt your interfaces.

My personal opinion is to create some kind of factory interface and register it somewhere in the deserializer.

The second one would be to have a factory class that exposes an implicit template interface (and use CRTP to specialize the interface to the users needs. i.e.:

template <class ActualType, 
          class IfType=ActualType,
          class Allocator=default::allocator<ActualType>>
class Deserializable {
  static IfType alloc(ActualType &&t) {
    Allocator a; // choose  another interface as your please.
    return a.allocate(t); /* implement me */
  }
private:
};

class MyClass
 : public InterfaceClass,
   public Deserializable<MyClass,InterfaceClass> {
  /* your stuff here */
};
  • This gives you a reasonable amount of abstraction in your template classes.
  • The user of your library knows what he wants in return anyway. and if he chooses to allocate something else than a std::shared_ptr he could do (by creating his own Allocator)
  • The user doesn't have to implement anything but specify types (and actually passes them to you, so no second guesses).

You could interpret this as a policy class (not in the strict sense of Andrei Alexandrescu). The serialization library mandates an allocation policy. The user can decide how this policy is implemented. In this case a choice on how to allocate the deserialized object and the type, which could be different. Because the Allocator has a default implementation and is a template argument, another choice is passed to the user if desired.

In order to understand the power of this approach I welcome you to look at the code of boost::operator that uses this technique to specify the return type and arguments of arithmetic operators at compile time.

Note

For people also looking at this post for answers of the original problem I'd suggest to use this approach. However it requires the member to be public, because it checks for a member function pointer of a given name.

like image 118
Alexander Oh Avatar answered Nov 16 '22 05:11

Alexander Oh