空隙和非空隙方法之间的Sfinae调度

SFINAE dispatch between void and non-void method

本文关键字:Sfinae 调度 方法 之间      更新时间:2023-10-16

我有以下内容:

template <typename T>
struct Base {
    auto func() {
        // do stuff
        auto x = static_cast<T&>(*this).func_impl();
        // do stuff
        return x;
    }
};
struct A : Base<A> {
    int func_impl() {
        return 0;
    }
};
struct B : Base<B> {
    void func_impl() {
    }
};
int main() {
    A a;
    int i = a.func();
    B b;
    b.func();
    return 0;
}

问题在于,我无法在派生类中声明func_impl的返回类型为void,如B所示。我试图使用这样的sfinae解决问题:

template <typename T>
struct Base {
    template <typename = enable_if_t<!is_void<decltype(declval<T>().func_impl())>::value>>
    auto func() {
        // do stuff
        auto x = static_cast<T&>(*this).func_impl();
        // do stuff
        return x;
    }
    template <typename = enable_if_t<is_void<decltype(declval<T>().func_impl())>::value>>
    void func() {
        // do stuff
        static_cast<T&>(*this).func_impl();
        // do stuff
    }
};

但是编译器给出了错误:invalid use of incomplete type 'struct A'invalid use of incomplete type 'struct B'。有没有办法完成我想要的东西?

尝试

template <typename T>
struct Base {
    template <typename U = T, typename = enable_if_t<!is_void<decltype(declval<U>().func_impl())>::value>>
    auto func() {
        // do stuff
        return static_cast<T&>(*this).func_impl();
    }
    template <typename U = T, typename = enable_if_t<is_void<decltype(declval<U>().func_impl())>::value>>
    void func() {
        // do stuff
        static_cast<T&>(*this).func_impl();
    }
};

我的意思是... Sfinae在模板上应用;如果要在类中启用/禁用方法,则必须是模板方法(类/struct是模板类/结构的事实不计算:方法是否必须为模板。

和sfinae部分(在这种情况下为std::enable_if_t)必须取决于该方法的模板(在我的示例中,U)。

P.S:无论如何,我看不到返回void

的问题

这样的情况:

auto x = static_cast<T&>(*this).func_impl();
// do stuff
return x;

呼叫常规空隙类型。换句话说,由于您不需要在这里需要 x,所以您只需要返回它,您真的需要需要 func()返回 void吗?我发现通常不是这种情况。人们不应该使用的任何旧的空类型都足够好。因此,让我们可以轻松地不复制而撰写此情况:

namespace details {
    struct Void { }; // not intended to be used anywhere
}
template <typename F, typename... Args,
    typename R = std::invoke_result_t<F, Args...>,
    std::enable_if_t<!std::is_void<R>::value, int> = 0>
R invoke_void(F&& f, Args&&... args) {
    return std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
}
template <typename F, typename... Args,
    typename R = std::invoke_result_t<F, Args...>,
    std::enable_if_t<std::is_void<R>::value, int> = 0>
details::Void invoke_void(F&& f, Args&&... args) {
    std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
    return details::Void{};
}

此实现使用C 17库功能,但可以在C 14中实现。这为我们提供了一个invoke(),可以将void换成Void,这使您只能写入:

auto func() {
    // do stuff
    auto x = invoke_void([](auto& x){ return x.func_impl(); },
        static_cast<T&>(*this));
    // do stuff
    return x;
}

这有点言语,但至少我们不必复制func()-只有一个功能可以处理两种情况。


另一种替代方案,根据您的解释更简单或更复杂,是重新排序func()的主体:

auto func() {
    // do stuff
    scope_exit{
        // do stuff after func_impl is invoked
    };
    return static_cast<T&>(*this).func_impl();
}

这使您可以正确订购操作,甚至不需要常规空隙。但是,func_impl后逻辑被放置在其之前 - 可能会令人困惑。但是好处是此功能仍然可以返回void

So上有许多scope_exit之类的东西的实现。