为什么我需要为传递给 Y 组合子的函数指定返回值?

3

我写了一个类似于Y组合子的函数:

template <class F>
struct Y{
  F f;
  Y(F _f) : f{_f} {}
  template<class...arg_t>
  auto operator()(arg_t&&...arg) {return f(*this,std::forward<arg_t>(arg)...);}
};

它可以工作,但是当我尝试定义一个阶乘时

auto fact = Y{[](auto&& self, int n) {
                if (n<=1) return 1;
                return n*self(n-1);}};

代码可以编译通过,但当我像f(3)这样调用它时,clang在推导返回类型时卡住了。加上显式的返回类型后,一切正常。这是模板推导的限制吗?有没有解决办法?


1
类型推导也需要一个固定点,但它无条件地适用于两个返回语句。 - Oliv
2个回答

2

我不认为有绕过这个的方法。您需要使用以下定义创建一个 Lambda:

[](auto&& self, int n) {
            if (n<=1) return 1;
            return n*self(n-1);
 }

这个翻译成:

struct lambda
 {
  template <typename T1>
  constexpr auto operator()(T1&&self, int n) const
   {
            if (n<=1)
                  return 1;
            return n*self(n-1);
    }
};

鉴于该代码,您的编译器应该将返回类型推断为两个返回语句的公共类型。

在您的模板实例化中,它首先需要知道实例化的返回类型,然后才能计算该实例化的答案。

对于这种特定情况,仍然可能正确地推断出它。如果您在其中添加额外的间接层,并回到您的类型,会发生什么?


我也有类似的想法,在godbolt上稍微尝试了一下后,就发现了你的答案。我使用了g++ -std=c++17来从另一个编译器得到“第二意见”。这是我做的:在godbolt上测试。有趣的是,它的行为与@Riley所描述的一样。只要注释掉调用fact(3),它就可以成功编译,甚至为auto fact创建了一些代码,尽管我不知道这有什么用处,也不知道它是否有任何有用的意义。 - Scheff's Cat
你的回答写得有些令人困惑。编译器必须检查所有返回语句表达式推导出的类型是否相同dcl.spec.auto。术语“通用类型”具有特殊含义(大致是条件表达式的衰减类型)。 - Oliv

1

类型推导被无条件地应用于Y组合子的两个返回语句,因为变量n所持有的信息不是常量表达式(编译器在编译时已知的表达式)。因此,类型推导无法找到固定点。

如果n的值在编译时已知,类型推导将成功,例如:

struct fact_overloads{
  template<class Self,int n>
  constexpr auto 
  operator()(Self&& self, std::integral_constant<n>){
    if constexpr (n<=1) return 1;
    else return n * self(std::integral_constant<n-1>{});
    };
  };

auto fact = Y{fact_overloads{}};

但是这样的函数具有一定的使用限制,因为n的值必须在编译时已知。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接