c++ - 仅当从 C++11 中的 B 派生时,如何有条件地调用 B::f?

标签 c++ templates c++11 c++14

如果使用静态多态性,尤其是在模板中(例如使用策略/策略模式),可能需要调用基函数成员,但您不知道实例化类实际上是否从该基派生。

这可以很容易地用旧的 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);
}

DEMO

将首先通过重载决议选择上层函数(由于 0int 完全匹配),如果 (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(); });
}

DEMO 2

关于c++ - 仅当从 C++11 中的 B 派生时,如何有条件地调用 B::f?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35717029/

相关文章:

c++ - 使用 boost::date_time 在当前时区获取当前时间的最简单方法?

c++ - 以字符数组为参数的模板元编程

c++ - 如何找到大数的斐波那契和?

c++ - 使用 enable_if 单独定义和声明模板成员函数,其模板参数还包含一个 constexpr 成员函数

c++ - Visual Studio 与 G++ 中的 Decltype 和友元函数

c++ - 为什么我要使用已删除的函数 'void std::ref(const _Tp&&) [with _Tp = int]'

c++ - 是否有等同于 gsl_multifit_wlinear() 的 Eigen C++ 库?

C++ 链接器错误

c++ - 我可以从类型列表中声明模板特化吗?

c++ - 带有 const 模板参数的模板模板类