Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to call conditionally B::f only if derived from B in C++11?

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:

  • Is there any better simple way (e.g. SFINAE doesn't look so) to achieve same result in C++11/C++14?
  • Would empty call of ellipsis parameter function be elided by optimizing compiler? Hope such case is not special against any "normal" function.
like image 690
Rost Avatar asked Mar 01 '16 07:03

Rost


3 Answers

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

like image 82
Piotr Skotnicki Avatar answered Oct 23 '22 00:10

Piotr Skotnicki


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

like image 21
relaxxx Avatar answered Oct 22 '22 23:10

relaxxx


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

like image 4
Rost Avatar answered Oct 23 '22 01:10

Rost