sfinae在递归功能中没有工作

发布于 2025-01-27 18:03:19 字数 3658 浏览 2 评论 0 原文

让我们创建咖喱函数。

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/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;

问题保持相同

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) )* );

但是 exptype(Declval&lt; f&gt; f&gt;()(1))*)失败。但是,由于模板替代失败,是否应该从超负荷分辨率中删除此功能?当 decouple 首次调用时,它起作用。但是,当被称为第二次时,Sfinae似乎被禁用,模板替换的第一个失败会产生汇编误差。次级Sfinae是否有某种限制?为什么递归调用模板函数不起作用?

该问题在GCC和Clang中复制。因此,它不是编译器错误。

Let's create currying function.

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

And function that decouple function to single argument functions:

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

Everything works fine if 2 arguments function is passed:

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

But if I add more arguments

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

The compilation breaks: 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...); }

It breaks in instantiation of std::is_invocable.

Since debugging the standard library is hard, I created simple versions of standard type traits classes:

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;

The problem remains the same 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>]'

It tries to resolve calling to this function:

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

But decltype (declval<F>()(1))*) fails. But shouldn't this function be removed from overload resolution because template substitution fails? It works when Decouple is called first time. But when it is called second time the SFINAE seems to be disabled, and the first failure of template substitution gives a compilation error. Are there some limitation on secondary SFINAE? Why calling template function recursively doesn't work?

The problem is reproduced in GCC and Clang. So it is not a compiler bug.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

请止步禁区 2025-02-03 18:03:19

您的 operator() Overload完全不受约束,因此声称与任何一组参数都可以调用。仅检查声明而不是定义,以确定在过载分辨率中呼叫的功能。如果替换为定义,则不适用Sfinae。

因此,将您的 operator()限制为 tfunc ,用 targ targs ... 作为参数可调用。

例如:

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

Your operator() overload is completely unconstrained and therefore claims to be callable with any set of arguments. Only declarations, not definitions, are inspected to determine which function to call in overload resolution. If substitution into the definition then fails, SFINAE does not apply.

So, constrain your operator() to require TFunc to be callable with TArg and TArgs... as arguments.

For example:

template <typename... TArgs>
auto operator()(TArgs ...args) const -> decltype(func(arg, args...))
苏别ゝ 2025-02-03 18:03:19

对我来说,奇怪的是,您的 curryt :: operator()接受未知数的参数。

由于目的是具有一个仅接受一个参数的函数,所以我期望此功能只接受一个参数。

IMO取决于哪种函数 curryt 保持 curryt :: operator()应返回其他类型:返回启动功能的类型或其他版本的 curryt

这是我使用 std :: bind_front 的方法,来自C ++ 20:

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:// https:// godbolt.org/z/ew9r4y6ea

用这种方法注明整数论点并不像您的解决方案中那样强制。

For me it is strange that your CurryT::operator() accepts unknown number of arguments.

Since aim is to have a functions which accept only one argument I expected that this function will accept only one argument.

IMO depending what kind of function CurryT holds CurryT::operator() should return a different type: return type of starting function or another version of CurryT.

Here is my approach using std::bind_front from C++20:

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

Note with this approach integer argument is not forced like in your solution.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文