是否有可能在C ++中创建一个可以自行调用的实际函数类型?

时间:2017-03-31 17:23:40

标签: c++ recursion

我试图通过Y-combinator编写递归而不引用C ++中的函数名。但是,我无法在以下尝试中找出函数的类型:

#include <iostream>

using std::cin;
using std::cout;

template<class Function> unsigned long factorial1(Function self, unsigned long n) {
    return n ? n * self(self, n - 1) : 1;
}

unsigned long factorial(unsigned long n) {
    return factorial1(factorial1, n);
}

int main() {
    unsigned long n;
    cin >> n;
    cout << factorial(n) << '\n';
    return 0;
}

编译器无法推断出Function是什么,我也不能。然后我尝试了以下内容:

#include <iostream>

using std::cin;
using std::cout;

struct Factorial {
    template<class Function> unsigned long operator()(Function self, unsigned long n) const {
        return n ? n * self(self, n - 1) : 1;
    }
};

unsigned long factorial(unsigned long n) {
    return Factorial()(Factorial(), n);
}

int main() {
    unsigned long n;
    cin >> n;
    cout << factorial(n) << '\n';
    return 0;
}

这与上面的例子相比,不同之处在于我将工作函数更改为可调用对象,Function可以轻松推导为Factorial,从而导致以下完整的组合实现:

#include <iostream>

using std::cin;
using std::cout;

struct Factorial {
    template<class Function> unsigned long operator()(Function self, unsigned long n) const {
        return n ? n * self(self, n - 1) : 1;
    }
};

template<class Function> auto y(Function f) {
    return [f](auto n) {
        return f(f, n);
    };
}

int main() {
    unsigned long n;
    cin >> n;
    cout << y(Factorial())(n) << '\n';
    return 0;
}

问题是,是否可以将结构Factorial重写为普通函数?

3 个答案:

答案 0 :(得分:2)

你做得有点错误:factorial1的第一个参数应该是factorial1的固定点,类型为unsigned long(*)(unsigned long),而不是factorial1本身,所以没有需要提供self作为论据:

unsigned long factorial1(unsigned long(*self)(unsigned long), unsigned long n) {
    return n ? n * self(n - 1) : 1;
}

C ++不允许将闭包作为函数指针传递,因此我们必须:

  • std::function或其他包装传递给self。没有意思的IMO。

  • 使用template magic在编译时生成定点函数。

第二个选项可以轻松完成:

template<class X, X(*Fn)(X(*)(X), X)>
struct Fix {
    static X Function(X x) {
        return Fn(Fix<X, Fn>::Function, x);
    }
};

现在,Fix<unsigned long, factorial1>::Functiona fixed point of factorial1

unsigned long factorial(unsigned long n) {
    return Fix<unsigned long, factorial1>::Function(n);
};

请注意,此Fix实现仍然按名称引用自身,因此任何没有类型系统黑客的定点组合器的实现也是如此。

答案 1 :(得分:0)

您无法直接传递模板,但您可以在飞行中使用通用lambda,因此最终看起来就像使用模板一样:

#define PASS_FUNC(name) [dummy=nullptr](auto&&... args){return name(decltype(args)(args)...);}

template<class Function> unsigned long factorial1(Function self, unsigned long n) {
    return n ? n * self(self, n - 1) : 1;
}

unsigned long factorial(unsigned long n) {
    return factorial1(PASS_FUNC(factorial1), n);
}

但我认为这是一个黑客攻击,因为lambdas仍然是函数对象。

答案 2 :(得分:-1)

您描述的案例看起来像无限类型或递归类型。如果您尝试自己手动推断类型,您可以看到它是无限的,您可能也是自己​​发现的类型。为了表明这一点,我想将您的factorial1函数简化为:

template <class T> void foobar(T self) {
    self(self);
}

然后尝试使用函数指针而不是模板编写此函数,以手动推断其类型。

首先,我们希望foobar将函数指针作为参数。

void foobar(void (*self)());
            ^^^^^^^^^^^^^^

但这仍然不是我们想要的,这个函数指针应该作为一个参数作为一个指向自己的指针。

void foobar(void (*self)(void (*)()));
                         ^^^^^^^^^^

但是我们还没有完成,因为我们必须再次添加一个指向自身的指针

void foobar(void (*self)(void (*)(void (*)())));
                                  ^^^^^^^^^^

你可以看到模式如何继续下去。

void foobar(void (*self)(void (*)(void (*)(void (*)()))));
                                           ^^^^^^^^^^

void foobar(void (*self)(void (*)(void (*)(void (*)(void (*)())))));
                                                    ^^^^^^^^^^

您给出的示例,您通过结构实现了这一点,只是通过operator()来模仿它。如果您将该函数的名称更改为foobar,它看起来像是:

struct Factorial {
    template<class Function> unsigned long foobar(Function self, unsigned long n) const {
        return n ? n * self.foobar(self, n - 1) : 1;
    }
};

unsigned long factorial(unsigned long n) {
    return Factorial().foobar(Factorial(), n);
}

所以你基本上在foobar中递归调用foobar,这与你的初始陈述相矛盾,你想在不知道/引用它的名字的情况下调用函数。