In case when static polymorphism is used, especially in templates (e.g. with policy/strategy pattern), it may be required to call base function member, but you don't know was instantiated class actually derived from this base or not.
This easily can be solved with old good C++ ellipsis overload trick:
#include <iostream>
template <class I>
struct if_derived_from
{
template <void (I::*f)()>
static void call(I& x) { (x.*f)(); }
static void call(...) { }
};
struct A { void reset() { std::cout << "reset A" << std::endl; } };
struct B { void reset() { std::cout << "reset B" << std::endl; } };
struct C { void reset() { std::cout << "reset C" << std::endl; } };
struct E: C { void reset() { std::cout << "reset E" << std::endl; } };
struct D: E {};
struct X: A, D {};
int main()
{
X x;
if_derived_from<A>::call<&A::reset>(x);
if_derived_from<B>::call<&B::reset>(x);
if_derived_from<C>::call<&C::reset>(x);
if_derived_from<E>::call<&E::reset>(x);
return 0;
}
The question is:
One option is to introduce two overloads of different priorities and to equip the preferred one with an expression SFINAE.
#include <utility>
template <typename T, typename... Args, typename C, typename R, typename... Params>
auto call_impl(int, R(C::*f)(Args...), T&& t, Params&&... params)
-> decltype((std::forward<T>(t).*f)(std::forward<Params>(params)...))
{
return (std::forward<T>(t).*f)(std::forward<Params>(params)...);
}
template <typename T, typename... Args, typename C, typename R, typename... Params>
void call_impl(char, R(C::*)(Args...), T&&, Params&&...)
{
}
template <typename T, typename... Args, typename C, typename R, typename... Params>
auto call(R(C::*f)(Args...), T&& t, Params&&... params)
-> decltype(call_impl(0, f, std::forward<T>(t), std::forward<Params>(params)...))
{
return call_impl(0, f, std::forward<T>(t), std::forward<Params>(params)...);
}
Test:
int main()
{
X x;
call(&B::reset, x);
}
DEMO
The upper function will be selected first by overload resolution (due to an exact match of 0
against int
), and possibly excluded from the set of viable candidates if (t.*f)(params...)
is not valid. In the latter case, the call to call_impl
falls back to the second overload, which is a no-op.
Given that &A::reset
may fail for multiple reasons, and you may not necessarily want to explicitly specify the function's signature, and, on top of that, you want the call to fail if the member function exists, but it does not match function call arguments, then you can exploit generic lambdas:
#include <utility>
#include <type_traits>
template <typename B, typename T, typename F
, std::enable_if_t<std::is_base_of<B, std::decay_t<T>>{}, int> = 0>
auto call(T&& t, F&& f)
-> decltype(std::forward<F>(f)(std::forward<T>(t)))
{
return std::forward<F>(f)(std::forward<T>(t));
}
template <typename B, typename T, typename F
, std::enable_if_t<!std::is_base_of<B, std::decay_t<T>>{}, int> = 0>
void call(T&& t, F&& f)
{
}
Test:
int main()
{
X x;
call<A>(x, [&](auto&& p) { return p.A::reset(); });
call<B>(x, [&](auto&& p) { return p.B::reset(); });
}
DEMO 2
what about something like:
#include <iostream>
#include <type_traits>
struct A { void reset() { std::cout << "reset A" << std::endl; } };
struct B { void reset() { std::cout << "reset B" << std::endl; } };
struct X :public A{};
template <typename T, typename R, typename BT>
typename std::enable_if<std::is_base_of<BT, T>::value, R>::type
call_if_possible(T & obj, R(BT::*mf)())
{
return (obj.*mf)();
}
template <typename T, typename R, typename BT>
typename std::enable_if<!std::is_base_of<BT, T>::value, R>::type
call_if_possible(T & obj, R(BT::*mf)()) { }
int main()
{
X x;
call_if_possible(x, &A::reset);
call_if_possible(x, &B::reset);
}
ideone
edit
maybe more readable way:
template <typename T, typename R, typename BT>
R call_if_possible_impl(T & obj, R(BT::*mf)(), std::false_type){}
template <typename T, typename R, typename BT>
R call_if_possible_impl(T & obj, R(BT::*mf)(), std::true_type)
{
return (obj.*mf)();
}
template <typename T, typename R, typename BT>
R call_if_possible(T & obj, R(BT::*mf)())
{
return call_if_possible_impl(obj, mf, typename std::is_base_of<BT, T>::type());
}
ideone
Basing on previously provided answers by @PiotrSkotnicki and @relaxxx I would like to combine the most simple and readable solution, without SFINAE and other blood-from-the-eyes things. It's just for reference, will not be accepted anyway:
#include <iostream>
#include <type_traits>
template <class Base, class Derived>
using check_base = typename std::is_base_of<Base, Derived>::type;
template <class Base, class Derived, typename Func>
void call(Derived& d, Func&& f)
{
call<Base>(d, std::forward<Func>(f), check_base<Base, Derived>());
}
template <class Base, typename Func>
void call(Base& b, Func&& f, std::true_type)
{
f(b);
}
template <class Base, class Derived, typename Func>
void call(Derived&, Func&&, std::false_type)
{
}
struct A { void reset(int i) { std::cout << "reset A: " << i << std::endl;} };
struct B { void reset() { std::cout << "reset B" << std::endl;} };
struct C { void reset() { std::cout << "reset C" << std::endl;} };
struct E: C { void reset() { std::cout << "reset E" << std::endl;} };
struct D: A, E {};
int main()
{
D d;
int i = 42;
call<A>(d, [&](auto& p) { p.reset(i); } );
call<B>(d, [](auto& p) { p.reset(); } );
call<C>(d, [](auto& p) { p.reset(); } );
call<E>(d, [](auto& p) { p.reset(); } );
}
Live at: http://cpp.sh/5tqa
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With