SFINAE在无效和非空方法之间发送

问题描述 投票:2回答:2

我有以下内容:

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'。有没有办法实现我想要的?

c++ templates c++14 sfinae
2个回答
5
投票

试试吧

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应用于模板;如果你想在类中启用/禁用方法,它们必须是模板方法(类/结构是模板类/结构不计算的事实:是必须是模板的方法。

SFINAE部分(在本例中为std::enable_if_t)必须依赖于该方法的模板(在我的示例中为U)。

P.s:无论如何,我没有看到返回void的问题


1
投票

这样的情况:

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

要求定期Void类型。换句话说,因为你在这里不需要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这样的实现。

© www.soinside.com 2019 - 2024. All rights reserved.