Thomas Anderson
Thomas Anderson

Reputation: 521

check if member exists and invoke it or do nothing

I have one template class with init() method which have to invoke subclass method if it exists. Method init() of Base class invokes forever.

template <class T>
class Base
{

   template<typename... Args>
   void init(Args... args);

   T subj;

   explicit Base() { subj = new T(); }
}


template<typename... Args>
Base<T>::init(Args... args)
{
    invoke_if_exists<&T::init,subj>(args); // <-- that moment
}

There is needed to implement invoke_if_exists template. The algorithm should be as code like that

if ( method_exists(T::init) )
{
     subj->init(Args...);
}   

I need it to be wrapped into template

Thank you so much.

[update]:

Let I try to explain little widely.

class Foo
{
// ... and there is no init()
};

class Right
{
    void init() { ... }
 /// ...
}


auto a = new Base<Foo>();
auto b = new Base<Right>();

a->init();  // there is no call for Foo::init();
b->init();  // there is correct call for Right::init();

I want to use invoke_if_exists<> not only with init() method, it could be any.

Upvotes: 4

Views: 586

Answers (1)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275435

A data and type sink, which take types and data, and do nothing with them:

struct sink {
  template<typename... Us>
  void operator()(Us&&... us) const {}
};
template<typename... Ts>
struct type_sink:std::true_type {};

we then create a template invoke_init_if_exists, which takes a target type T and a set of arguments, and if T*->init can be invoked with these arguments, does so. Otherwise, it throws the stuff into the data sink above:

template<typename T,typename ArgTypes,typename=void>
struct invoke_init_if_exists:sink {};
template<typename T,template<typename...>class Pack,typename... Args>
struct invoke_init_if_exists<T,
  Pack<Args...>,
  typename std::enable_if< type_sink<
    decltype( std::declval<T*>()->init( std::declval<Args>()... ) )
  >::value >::type
>
{
  void operator()( T* target, Args&&... args ) const {
    target->init( std::forward<Args>(args)... );
  }
};

We then use this to implement your init method:

template<typename... Args>
Base<T>::init(Args&&... args)
{
  invoke_init_if_exists<T, type_sink<Args...>>()(&subj, std::forward<Args>(args)...);
}

where we create our helper class, which is either a sink or something that forwards to init depending on if the call to init is valid.

We end up having to do something like this because you cannot name the token T::init with validity unless T already has a method init. So we have to write a custom class that does the T::init lookup in a SFINAE context, and if it fails falls back to doing nothing.

Upvotes: 3

Related Questions