Как условно назвать 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?
- Будет ли пустой вызов функции параметра эллипсиса отменен оптимизацией компилятора? Надеюсь, что такой случай не является особенным против какой-либо "нормальной" функции.
Ответы
Ответ 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