有没有办法根据参数值执行函数签名匹配?

时间:2016-09-14 14:24:53

标签: c++

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

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

当参数为0时,factorial的定义与参数为任何其他整数时的factorial定义不同。

我还没有在C ++中找到这种功能,并且首先想到用语言实现它很难。进一步的反思使我觉得它实际上相当容易,并且是语言的一个很好的补充,所以我必须错过它。

有没有办法在本机语法或模板中执行此操作?

6 个答案:

答案 0 :(得分:2)

有这样的事情,它被称为模板专业化。基本上,除了通用模板定义之外,您还可以为给定类型定义模板。你可以阅读它here

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;

阶乘模板&#34;功能&#34;也使用模板特化,但由于模板的性质,它只能计算编译时间值(模板元编程):

{{1}}

据我所知,在给定函数中运行时没有这样的东西(除了switch / if branches)。

答案 1 :(得分:2)

我认为这里真正的答案是没有确切的等价物。然而。模板特化很接近,但只能在编译时工作,有些限制了它的可用性。我们当然有分支,但与其他函数式编程语言中的模式匹配相比,功能有限。

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

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

我对语法不完全确定,但话说回来,它只是一个提案,所以语法本身可能会改变。

答案 2 :(得分:1)

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

完成
//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;
}

答案 3 :(得分:0)

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

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

答案 4 :(得分:0)

运行时分支是使用if或三元运算符完成的 <condition> ? <if-true> : <if-false>

函数的重载在编译时完成,因此这意味着如果要根据值选择函数的重载,则必须在编译时严格知道该值。

以下是使用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);
} 

答案 5 :(得分:0)

int factorial(int n)
{
    switch(n)
    {
        case 0: return 1;
        default: return n * factorial(n - 1);
    }
}