如果使用静态多态性,尤其是在模板中(例如使用策略/策略模式),可能需要调用基函数成员,但您不知道实例化类实际上是否从该基派生。
这可以很容易地用旧的 C++ 省略号重载技巧来解决:
#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 看起来不是这样)在 C++11/C++14 中实现相同的结果?
- 优化编译器会省略省略号参数函数的空调用吗?希望这种情况对任何“正常”功能都不是特别的。
最佳答案
一种选择是引入两个不同优先级的重载,并为首选的一个配备表达式 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);
}
将首先通过重载决议选择上层函数(由于 0
与 int
完全匹配),如果 (t.*f)(params...)
无效。在后一种情况下,对 call_impl
的调用会退回到第二个重载,这是一个无操作。
鉴于 &A::reset
可能由于多种原因而失败,并且您可能不一定要明确指定函数的签名,并且除此之外,您希望调用失败,如果成员函数存在,但它与函数调用参数不匹配,则可以利用泛型 lambda:
#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(); });
}
关于c++ - 仅当从 C++11 中的 B 派生时,如何有条件地调用 B::f?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35717029/