用户自定义字面量参数不是constexpr吗?

23
我正在测试用户定义的字面量,我希望 _fac 返回数字的阶乘。
让它调用一个 constexpr 函数是可行的,但是无法使用模板,因为编译器会抱怨参数不是 constexpr,也不能成为 constexpr
我对此感到困惑 - 字面量不是常量表达式吗?在 5_fac 中的 5 始终是可以在编译时评估的文字,那么为什么我不能将其作为这样使用呢?
第一种方法:
constexpr int factorial_function(int x) {
  return (x > 0) ? x * factorial_function(x - 1) : 1;
}

constexpr int operator "" _fac(unsigned long long x) {
  return factorial_function(x); // this works
}

第二种方法:

template <int N> struct factorial_template {
  static const unsigned int value = N * factorial_template<N - 1>::value;
};
template <> struct factorial_template<0> {
  static const unsigned int value = 1;
};

constexpr int operator "" _fac(unsigned long long x) {
  return factorial_template<x>::value; // doesn't work - x is not a constexpr
}

我认为目前没有任何编译器实现这些用户定义的字面量。你是如何测试这个的? - Kerrek SB
不错!我不知道那已经被支持了。我有一台安装了这些东西的机器,现在我很想尝试使用可变参数模板 :-) - Kerrek SB
5个回答

12

我不知道在C++11中是否有比当前接受的答案更好的方法,但是在C++14中使用放宽了的constexpr关键字,你可以只需编写 "普通 "代码:

constexpr unsigned long long int operator "" _fac(unsigned long long int x) {
    unsigned long long int result = 1;
    for (; x >= 2; --x) {
        result *= x;
    }
    return result;
}

static_assert(5_fac == 120, "!");

5
这是我最终完成的方法:
template <typename t>
constexpr t pow(t base, int exp) {
  return (exp > 0) ? base * pow(base, exp-1) : 1;
};

template <char...> struct literal;
template <> struct literal<> {
  static const unsigned int to_int = 0;
};
template <char c, char ...cv> struct literal<c, cv...> {
  static const unsigned int to_int = (c - '0') * pow(10, sizeof...(cv)) + literal<cv...>::to_int;
};

template <int N> struct factorial {
  static const unsigned int value = N * factorial<N - 1>::value;
};
template <> struct factorial<0> {
  static const unsigned int value = 1;
};

template <char ...cv>
constexpr unsigned int operator "" _fac()
{
  return factorial<literal<cv...>::to_int>::value;
}

感谢KerrekSB大力相助!


2
下降投票,因为答案并没有真正回答OP所提出的问题,而只包含代码。 - jotik

4

我可能错了,但我认为constexpr函数也可以使用非常量参数调用(在这种情况下,它们不会给出常量表达式并在运行时进行评估)。这对于非类型模板参数不起作用。


嗯,假设如果您将非编译时常量传递给函数,编译器应该会发出警告。这可能是解决模板性在代码层级中向上渗透的问题的一种方式吗? - Michael Price
@MichaelPrice:当然,它可以抱怨将非const表达式传递给constexpr函数(正如我所说,我不确定我是否正确)。但这意味着通常需要两个函数,除了“constexpr”关键字外,其余完全相同,一个用于常量表达式,另一个用于所有其他表达式。 - celtschk

3
为了能够将constexpr与用户定义字面量结合使用,显然需要使用可变参数模板。请参考wikipedia article中的第二个示例。

你并不总是需要使用可变参数模板来使用constexpr,但这种情况确实需要它(但我知道你的意思)。 - Seth Carnegie
如何将一个可变参数的 char 模板转换为 int - Pubby
@Pubby - 从中减去0x30?我不确定是否有更好的方法,但你可能可以使用部分模板特化来实现...虽然这个想法让我头疼。 - Nate
2
为什么不提供一个示例,而不是引用维基百科? - mceo

1
@Pubby。消化字符非类型参数包的简单方法是将其捕获到字符串的初始化列表中。然后,您可以使用atoi、atof等函数:
#include <iostream>

template<char... Chars>
  int
  operator "" _suffix()
  {
    const char str[]{Chars..., '\0'};
    return atoi(str);
  }

int
main()
{
  std::cout << 12345_suffix << std::endl;
}

记得为 C 风格的函数添加一个空字符。


问题在于它不是“constexpr”。实际上,这比将“unsigned long long”作为参数类型传递更糟糕。 - Pubby

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