SFINAE 在递归函数中不起作用

SFINAE doesn't work in recursive function

让我们创建 currying 函数。

template <typename TFunc, typename TArg>
class CurryT
{
public:
    CurryT(const TFunc &func, const TArg &arg)
      : func(func), arg(arg )
        {}

    template <typename... TArgs>
        decltype(auto) operator()(TArgs ...args) const
            { return func(arg, args...); }

private:
    TFunc func;
    TArg  arg ;
};

template <typename TFunc, typename TArg>
    CurryT<decay_t<TFunc>, remove_cv_t<TArg>>
        Curry(const TFunc &func, const TArg &arg)
            { return {func, arg}; }

以及将函数解耦为单参数函数的函数:

// If single argument function (F(int)).
template <typename F>
    static auto Decouple(const F &f, enable_if_t<is_invocable_v<F, int>> * = nullptr)
    {
        return f;
    }

// If multiple arguments function (F(int, int, ...)).
template <typename F>
    static auto Decouple(const F &f, enable_if_t<!is_invocable_v<F, int>> * = nullptr)
    {
        return [f](int v) { return Decouple( Curry(f, v) ); };
    }

如果传递 2 个参数函数,一切正常:

auto f1 = Decouple(
    [](int a, int b)
        { std::cout << a << " " << b << std::endl; }
);
f1(3)(4); // Outputs 3 4

但是如果我添加更多参数

auto f2 = Decouple(
    [](int a, int b, int c)
        { std::cout << a << " " << b << " " << c << std::endl; }
);
f(5)(6)(7);

编译中断:https://coliru.stacked-crooked.com/a/10c6dba670d17ffa

main.cpp: In instantiation of 'decltype(auto) CurryT<TFunc, TArg>::operator()(TArgs ...) const [with TArgs = {int}; TFunc = main()::<lambda(int, int, int)>; TArg = int]':

main.cpp:17:26: error: no match for call to '(const main()::<lambda(int, int, int)>) (const int&, int&)'
   17 |             { return func(arg, args...); }

它在 std::is_invocable 的实例化中中断。

由于很难调试标准库,我创建了标准类型特征的简单版本 类:

template <typename F> true_type  check(const F &, decltype( declval<F>()(1) )* );
template <typename F> false_type check(const F &, ...);

template <typename F>
    struct invocable_with_int : decltype(check(declval<F>(), nullptr))
        {};

template <typename F>
    inline constexpr bool invocable_with_int_v = invocable_with_int<F>::value;

template<bool B>
    struct my_enable_if {};

template<>
    struct my_enable_if<true>
        { using type = void; };

template <bool B>
    using my_enable_if_t = typename my_enable_if<B>::type;

问题依旧https://coliru.stacked-crooked.com/a/722a2041600799b0:

main.cpp:29:73:   required by substitution of 'template<class F> std::true_type check(const F&, decltype (declval<F>()(1))*) [with F = CurryT<main()::<lambda(int, int, int)>, int>]'

它试图解决对该函数的调用:

template <typename F> true_type  check(const F &, decltype( declval<F>()(1) )* );

但是 decltype (declval<F>()(1))*) 失败了。但是这个函数不应该因为模板替换失败而从重载决议中删除吗?它在第一次调用 Decouple 时起作用。但是当它第二次被调用时,SFINAE 似乎被禁用了,并且模板替换的第一次失败给出了编译错误。二级 SFINAE 有一些限制吗?为什么递归调用模板函数不起作用?

问题在 GCC 和 Clang 中重现。所以这不是编译器错误。

您的 operator() 重载完全不受约束,因此声称可以使用任何参数集调用。只检查声明,而不检查定义,以确定在重载决策中调用哪个函数。如果代入定义失败,则 SFINAE 不适用。

因此,限制您的 operator() 要求 TFunc 可以使用 TArgTArgs... 作为参数进行调用。

例如:

template <typename... TArgs>
auto operator()(TArgs ...args) const -> decltype(func(arg, args...))

对我来说,你的 CurryT::operator() 接受未知数量的参数是很奇怪的。

因为目标是有一个只接受一个参数的函数,所以我希望这个函数只接受一个参数。

IMO 取决于 CurryT 持有哪种函数 CurryT::operator() 应该 return 不同的类型:return 类型的起始函数或 CurryT 的另一个版本.

这是我使用 C++20 中的 std::bind_front 的方法:

namespace detail {
template <typename TFunc>
class CurryT
{
public:
    explicit CurryT(TFunc f) : mF(std::move(f))
    {}

    template<typename T>
    auto get(T&& x, int = 0) -> decltype(std::declval<TFunc>()(x)) {
        return mF(x);
    }

    template<typename T>
    auto get(T&& x, char) {
        return CurryT<decltype(std::bind_front(mF, std::forward<T>(x)))>{
            std::bind_front(mF, std::forward<T>(x))
        };
    }

    template<typename T>
    auto operator()(T&& x)
    {
        return this->get(std::forward<T>(x), 1);
    }
private:
     TFunc mF;
};
}

template<typename F>
auto Decouple(F&& f)
{
    return detail::CurryT<std::decay_t<F>>{std::forward<F>(f)};
}

https://godbolt.org/z/eW9r4Y6Ea

请注意,此方法不会像您的解决方案那样强制使用整数参数。