如果从C++ 11中的B派生出来,如何有条件地调用B :: f?

Ros*_*ost 20 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;
}
Run Code Online (Sandbox Code Playgroud)

问题是:

  • 有没有更好的简单方法(例如SFINAE看起来不那么)在C++ 11/C++ 14中实现相同的结果?
  • 优化编译器会省略省略号参数函数的空调用吗?希望这种情况对任何"正常"功能都不是特别的.

Pio*_*cki 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)...);
}
Run Code Online (Sandbox Code Playgroud)

测试:

int main()
{
    X x;
    call(&B::reset, x);
}
Run Code Online (Sandbox Code Playgroud)

DEMO

上功能将首先通过重载解析来选择(由于精确匹配0int),以及可能的来自该组的可行候选的排除在外,如果(t.*f)(params...)是无效的.在后一种情况下,调用将call_impl回退到第二个重载,这是一个无操作.


鉴于&A::reset可能由于多种原因而失败,并且您可能不一定要明确指定函数的签名,并且,除此之外,如果成员函数存在,您希望调用失败,但它与函数调用参数不匹配,那么你可以利用通用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)
{
}
Run Code Online (Sandbox Code Playgroud)

测试:

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

演示2


rel*_*xxx 13

怎么样的:

#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);
}
Run Code Online (Sandbox Code Playgroud)

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());
}
Run Code Online (Sandbox Code Playgroud)

ideone

  • @IsmaelMiguel那是因为它不是C或C#;) (2认同)