【问题标题】:Is there any way to perform function signature matching based on parameter values?有没有办法根据参数值执行函数签名匹配?
【发布时间】:2016-09-14 14:24:53
【问题描述】:

在 Haskell 等面向函数的语言中,可以将函数定义重载到多个参数签名轴。 C++ 支持参数的数量和类型。其他语言支持参数值甚至保护子句(测试参数的条件的代码。)例如 Haskell 中的阶乘实现:

factorial :: (Integral a) => a -> a  
factorial 0 = 1  
factorial n = n * factorial (n - 1) 

参数为 0 时的阶乘定义与参数为任何其他整数时的阶乘定义不同。

我在 C++ 中没有发现这种能力,起初我认为用这种语言实现会很困难。进一步的思考让我觉得它实际上会相当简单,并且是对语言的一个很好的补充,所以我一定错过了它。

有没有办法在本机语法或模板中做到这一点?

【问题讨论】:

  • C++ 模板可以专门用于整数常量,这与您的 Haskell 示例大致相同。 Here is a factorial function written with specialized templates in C++.
  • 是的,它可以通过模板元编程来完成,但前提是参数值在编译时是已知的。 C++ 不能在运行时对参数值进行分派,只能通过虚拟方法分派。
  • 这不是两个重载的函数,而是函数factorial x = case x of 0 => 1; n => n * factorial (n - 1)上面撒了句法糖。

标签: c++


【解决方案1】:

我认为这里真正的答案是没有完全等价的。然而。模板专业化很接近,但仅在编译时有效,这有几个限制了它的可用性。我们当然有分支,但与模式匹配在其他函数式编程语言中的功能相比,它的功能有限。

目前有一个关于 C++ 模式匹配的提议:P0095r1,假设概念允许以下阶乘定义:

template <Integral I>
I factorial(I n) {
    return inspect(n) {
        0 => 1
        n => n * factorial(n-1)
    };
}

我不完全确定语法,但话说回来,到目前为止,这只是一个提议,所以语法本身可能会改变。

【讨论】:

    【解决方案2】:

    有这样一个东西,叫做模板特化。基本上,除了通用模板定义之外,您还可以为给定类型定义模板。你可以阅读它here

    //Main template definition
    template<typename T>
    void foo(T) { std::cout << "Some T\n"; }
    
    //Specialization for int
    template<>
    void foo(int) { std::cout << "Called with an int!\n"; }
    

    阶乘模板“函数”也使用模板特化,但由于模板的性质,它只能计算编译时值(模板元编程):

    template<std::size_t N>
    struct factorial {
        static constexpr unsigned long long value = N * factorial<N - 1>::value;
    };
    
    template<>
    struct factorial<0> {
        static constexpr unsigned long long value = 1;
    }
    
    auto foo = factorial<10>::value;
    

    据我所知,给定函数在运行时没有这样的事情(switch/if 分支除外)。

    【讨论】:

      【解决方案3】:

      如果值在编译时已知,则可以使用模板完成

      //recursively calls itself until N is 1
      template<int N>
      struct factorial<N>{enum{value = N * factorial<N-1>::value};};
      
      //at which point, this will be called (stopping the recursion)
      template<>
      struct factorial<1>{enum{value = 1};};
      

      如果这些值仅在运行时已知,则必须在运行时做出决定

      int factorial_recursion(int n){
        if(n == 1)
          return 1;
        else
          return n * factorial_recursion(n - 1);
      }
      //or
      int factorial_loop(int n){
        int answer = 1;
        for(int count = n; count > 1; --count)
          answer *= count;
      
        return answer;
      }
      

      【讨论】:

        【解决方案4】:

        简答:没有 C++ 没有 Haskell 样式的模式匹配。另外值得一提的是,在 Haskell 示例中,您只有一个函数,而不是其中的两个,但您只有一个更好的语法来检查输入的值。在重载中,您实际上有两个或多个具有相同名称但参数数量或类型不同的函数。

        更长/更真实的答案:通过template-metaprogramming 可以实现类似于您所建议的内容。由于 C++ 模板允许将值作为模板参数,而不仅仅是类型,因此您实际上可以构建这样的函数。模板语言显然是图灵完备的,因此您实际上可以计算可以用它计算的所有内容。 当然,它看起来很糟糕,并且会导致大量编译时间以及在初始编写后难以理解代码。

        【讨论】:

          【解决方案5】:

          运行时分支是使用if 或三元运算符完成的 &lt;condition&gt; ? &lt;if-true&gt; : &lt;if-false&gt;.

          函数的重载是在编译时完成的,所以这意味着如果你想根据值选择一个函数的重载,你必须在编译时严格地知道这个值。 p>

          这是一个使用 sfinae 进行编译时分支的示例:

          template<int n, std::enable_if_t<(n > 1), short> = 0>
          constexpr int factorial(std::integral_constant<int, n>) {
              return n * factorial(std::integral_constant<n - 1>{});
          }
          
          template<int n, std::enable_if_t<(n == 0), short> = 0>
          constexpr int factorial(std::integral_constant<int, n>) { return 1; }
          

          在这里,请注意enable_if_t 中使用的条件。如果条件不满足,函数调用无效,并强制编译器尝试替代函数。

          当然,语法不是那么好。最好的办法是在运行时和编译时都有一个实现,但为此你必须使用传统的分支:

          constexpr factorial(int n) {
              return n == 0 ? 1 : n * factorial(n - 1);
          } 
          

          【讨论】:

            【解决方案6】:
            int factorial(int n)
            {
                switch(n)
                {
                    case 0: return 1;
                    default: return n * factorial(n - 1);
                }
            }
            

            【讨论】:

            • 我的想法是编译器会/可能/可能会创建这个“幕后”。
            • @JasonDoege 我认为 C++ 是关于明确性的。我认为让编译器在人们背后耍花招会违反该语言的精神。我认识像 Haskell 这样的人,但对我来说,语法非常神秘,而且(除了这个微不足道的案例)非常难以推理。我不知道也许只是我内心深处是一个老机器代码黑客:)
            猜你喜欢
            • 2020-01-09
            • 2019-06-16
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2010-09-13
            • 1970-01-01
            • 2019-09-04
            相关资源
            最近更新 更多