4

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.

Thomas Anderson
  • 511
  • 1
  • 5
  • 14
  • possible duplicate of http://stackoverflow.com/questions/87372/check-if-a-class-has-a-member-function-of-a-given-signature – IdeaHat Nov 13 '13 at 16:57
  • Only one thing: template should be expanded only if method exists, otherwise empty code 'cause we'll get compiler error like "error: SomeClass has no member named 'method'" – Thomas Anderson Nov 13 '13 at 17:06
  • 1
    `invoke_if_exists<&T::init,subj>(args...);` (your forgot the expansion) – dyp Nov 13 '13 at 17:16
  • @MadScienceDreams not quite, as you don't need to match exact signatures here. – Yakk - Adam Nevraumont Nov 13 '13 at 18:36
  • You can look at the example in this question: http://stackoverflow.com/questions/3957348/what-is-the-practical-use-of-pointers-to-member-functions maybe the methods I use there will be of use ... – slashmais Nov 13 '13 at 18:53
  • @slashmais you kinda do, you need T::init to be a void T::init(Args...). – IdeaHat Nov 13 '13 at 19:08
  • @MadScienceDreams no, you need it to accept `Args`. You don't need its signature to be exactly `Args`. As an example, if someone calls `init(7)`, you might be surprised that `T::init(long)` wouldn't be called, and if you look for exact signatures, that is exactly what would happen. Ditto with return type. – Yakk - Adam Nevraumont Nov 13 '13 at 19:16
  • @MadScienceDreams: I've been reading your link to ../87372/.. and it's fascinating (also makes me wonder how many raw beginners in C++ throw their hands up at the complexity/obscurity and walk away ;) – slashmais Nov 13 '13 at 19:24

1 Answers1

3

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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • 2
    Be aware that this might be error prone: If you supply an invalid set of arguments for a type `T` that *does* have an `init` function, you'll get no compile-time error. (As opposed to a SFINAE check if `init` exists at all and then calling it with the provided arguments.) – dyp Nov 13 '13 at 19:31
  • @DyP yep. But in C++, you cannot in general determine if there is a method `init` on a type `T`. You can ask "is there a method `init` with exactly the signature of `R(Args...)`", or you can ask "is there a method `init` that can be called with `Args...` and returns something convertible to `R`", or linear combinations thereof. You can also ask "is there a single, solitary, non-overridden, `T::init` non-`template` method." Each has its own issues. – Yakk - Adam Nevraumont Nov 13 '13 at 19:50
  • So if I truly understand I can't define template without determining specific class method and then explicit call of it `target->init( ... )`. I mean, is there no way to forward object and the pointer on to its method using your invoke template? – Thomas Anderson Nov 13 '13 at 20:56
  • @Shozabred the above will invoke `init` with no args if there is an `init` with no args in type type `T`. You have to write a custom helper for each name of function (you cannot write `invoke_if_exists`, but you can write `invoke_foo_if_exists`). – Yakk - Adam Nevraumont Nov 13 '13 at 22:07