【问题标题】:SFINAE dispatch between void and non-void methodvoid 和非 void 方法之间的 SFINAE 调度
【发布时间】:2017-12-19 15:51:52
【问题描述】:

我有类似以下的内容:

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

【问题讨论】:

  • Works here. 为什么你认为不能将func_impl 的返回类型声明为void?
  • 你是对的。谢谢!实际上我在func_impl-call 之后也做了一些事情,所以我必须将值保存到一个变量中并在之后返回它。我现在改了问题。

标签: c++ templates c++14 sfinae


【解决方案1】:

试试

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 的问题

【讨论】:

  • 我认为主要问题是分配给变量x。他本可以完成return static_cast&lt;T&amp;&gt;(*this).func_impl() 并且它会起作用的。
  • @0x499602D2 - 嗯......我想我们应该看到第二个“做的东西”部分但是......是的,我同意......避免T变量并直接返回计算值会起作用。
【解决方案2】:

这样的情况:

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();
}

这可以让您获得正确的操作顺序,甚至不需要常规的 void。然而,post-func_impl 逻辑被放置在它之前——这可能会令人困惑。但好处是这个函数仍然可以返回void

在 SO 上有很多类似 scope_exit 的实现。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-05-04
    • 2015-09-26
    • 2014-09-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多