Подтвердить что ты не робот

Как условно назвать B:: f, только если он получен из B в С++ 11?

В случае использования статического полиморфизма, особенно в шаблонах (например, с шаблоном политики/стратегии), может потребоваться вызвать базовый элемент функции, но вы не знаете, был ли экземпляр класса фактически полученным из этой базы или нет.

Это легко может быть решена с помощью старого хорошего эллиптического трюка С++:

#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;
}

Возникает вопрос:

  • Есть ли более простой способ (например, SFINAE не выглядит так), чтобы достичь такого же результата в С++ 11/С++ 14?
  • Будет ли пустой вызов функции параметра эллипсиса отменен оптимизацией компилятора? Надеюсь, что такой случай не является особенным против какой-либо "нормальной" функции.
4b9b3361

Ответ 1

Один из вариантов состоит в том, чтобы ввести две перегрузки разных приоритетов и оснастить предпочтительное выражение 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)...);
}

Тест:

int main()
{
    X x;
    call(&B::reset, x);
}

DEMO

Верхняя функция будет выбрана сначала с помощью разрешения перегрузки (из-за точного соответствия 0 против int) и, возможно, исключена из набора жизнеспособных кандидатов, если (t.*f)(params...) недействительна. В последнем случае вызов call_impl возвращается ко второй перегрузке, которая является не-оператором.


Учитывая, что &A::reset может выйти из строя по нескольким причинам, и вы можете не обязательно явно указывать подпись функции, и, кроме того, вы хотите, чтобы вызов завершился с ошибкой, если существует функция-член, но она не аргументы вызова функции функции, тогда вы можете использовать общие лямбда:

#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)
{
}

Тест:

int main()
{
    X x;
    call<A>(x, [&](auto&& p) { return p.A::reset(); });
    call<B>(x, [&](auto&& p) { return p.B::reset(); });
}

DEMO 2

Ответ 2

как насчет чего-то типа:

#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

изменить

может быть более читаемым способом:

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

Ответ 3

Основываясь на ранее предоставленных ответах @PiotrSkotnicki и @relaxxx, я хотел бы объединить самое простое и понятное решение без SFINAE и других вещей, связанных с кровью. Он просто для справки, не будет принят в любом случае:

#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