当前位置: 首页 > 知识库问答 >
问题:

为什么 C 编译器可以将函数声明为 constexpr,而 constexpr 不能是 constexpr?

孟树
2023-03-14

为什么 C 编译器可以将函数声明为 constexpr,而 constexpr 不能是 constexpr?

例如:http://melpon.org/wandbox/permlink/AGwniRNRbfmXfj8r

#include <iostream>
#include <functional>
#include <numeric>
#include <initializer_list>

template<typename Functor, typename T, size_t N>
T constexpr reduce(Functor f, T(&arr)[N]) {
  return std::accumulate(std::next(std::begin(arr)), std::end(arr), *(std::begin(arr)), f);
}

template<typename Functor, typename T>
T constexpr reduce(Functor f, std::initializer_list<T> il) {
  return std::accumulate(std::next(il.begin()), il.end(), *(il.begin()), f);
}

template<typename Functor, typename T, typename... Ts>
T constexpr reduce(Functor f, T t1, Ts... ts) {
  return f(t1, reduce(f, std::initializer_list<T>({ts...})));
}

int constexpr constexpr_func() { return 2; }

template<int value>
void print_constexpr() { std::cout << value << std::endl; }

int main() {
  std::cout << reduce(std::plus<int>(), 1, 2, 3, 4, 5, 6, 7) << std::endl;  // 28
  std::cout << reduce(std::plus<int>(), {1, 2, 3, 4, 5, 6, 7}) << std::endl;// 28

  const int input[3] = {1, 2, 3};   // 6
  std::cout << reduce(std::plus<int>(), input) << std::endl;

  print_constexpr<5>(); // OK
  print_constexpr<constexpr_func()>();  // OK
  //print_constexpr<reduce(std::plus<int>(), {1, 2, 3, 4, 5, 6, 7})>(); // error 

  return 0;
}

输出:

28
28
6
5
2

为什么此行出错://print_constexpr

  • std::p lus - constexpr T 运算符()( const T

为什么编译器允许将reduce()标记为constexpr,但即使在编译时传递给reduce()的所有参数都是已知的,也不能将reduce?

对于一些支持C 14-std=c 14的编译器具有相同的效果:

  • x86 GCC 7.0.0-std=c 1z-O3:http://melpon.org/wandbox/permlink/AGwniRNRbfmXfj8r
  • x86 gcc 4.9.2-std=c 14-O3:https://godbolt.org/g/wmAaDT
  • x86 gcc 6.1-std=c 14-O3:https://godbolt.org/g/WjJQE5
  • x86 clang 3.5-std=c 14-O3:https://godbolt.org/g/DSCpYv
  • x86 clang 3.8-std=c 14-O3:https://godbolt.org/g/orSrgH
  • x86 Visual C-您应该将代码复制粘贴到:http://webcompiler.cloudapp.net/
  • ARM gcc 4.8.2、ARM64 gcc 4.8、PowerPC gcc 4.8、AVR gcc 4.5.3-不支持C 14-std=c 14

对于所有这些情况,编译OK,直到未使用的行:< code>//print_constexpr


共有3个答案

郑宜民
2023-03-14

如果您确实编写了此代码:

  constexpr int result = reduce(std::plus<int>(), {1, 2, 3, 4, 5, 6, 7});

您将看到reduce不会产生constexpr结果。

原因是因为“注意:非 constexpr 函数”累积

std::accumulate不是constexpr

编辑扩展以回答实际问题,谢谢@peterchen:它是在达到用法时编译的,在编译模板的特定版本之前,它不会也无法尝试解析函数。当它遇到用法并触发编译时,它会解决累积并看到它不是 constexpr,因此发出错误。

薛扬
2023-03-14

让我们直接从它的提案开始,www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2235.pdf 在第 4.1 节第三段中:我引用:

可以使用非常量表达式调用常量表达式函数,在这种情况下,不需要在编译时计算结果值。

请参阅此问题:constexpr 函数何时在编译时被计算?

template<typename Functor, typename T>
T constexpr reduce(Functor f, std::initializer_list<T> il) {
  return std::accumulate(std::next(il.begin()), il.end(), *(il.begin()), f);
}

同样,如您所知,< code>std::accumulate不是一个< code>constexpr函数。

template<int value>
void print_constexpr() { std::cout << value << std::endl; }

同样,如您所知,非类型模板参数必须是常量表达式。

现在:

template<typename Functor, typename T>
T constexpr reduce(Functor f, std::initializer_list<T> il) {
  return std::accumulate(std::next(il.begin()), il.end(), *(il.begin()), f);
}

至于为什么会这样:C标准是这么说的:

[dcl.constexpr/6](强调我的):

如果 constexpr 函数模板的实例化模板专用化或类模板的成员函数无法满足 constexpr 函数或 constexpr 构造函数的要求,则该专用化仍然是 constexpr 函数或 constexpr 构造函数,即使对此类函数的调用不能出现在常量表达式中......

注意:那个

从函数模板实例化的函数称为函数模板专门化;

如果不是模板,它将失败:

int constexpr reduce(int(*f)(int, int), std::initializer_list<int> il) {
  return std::accumulate(std::next(il.begin()), il.end(), *(il.begin()), f);
}

编译器现在会抱怨,不能在定义constexpr的函数中调用非constexprfunction

云宏儒
2023-03-14
匿名用户

为什么 C 编译器可以将函数声明为 constexpr,而 constexpr 不能是 constexpr?

它没有。但您没有定义函数。您正在定义一个模板。

让我们设置:

struct Is_constexpr {
  constexpr Is_constexpr() = default;
  constexpr auto bar() {
    return 24;
  }
};

struct Not_constexpr {
  auto bar() {
    return 24;
  }
};

现在,如果您尝试将一个函数(而不是模板)定义为使用Not_constexpr编译器不允许您:

constexpr auto foo_function(Not_constexpr v)
{
  return v.bar();
  // error: call to non-constexpr function 'auto Not_constexpr::bar()'
}

但是,您正在定义一个模板。让我们看看这是怎么回事:

template <class T>
constexpr auto foo(T v)
{
  return v.bar();
}

编译器允许您这样做。没有错误。为什么?因为是模板。有些实例化可能是< code>constexpr,有些不是,这取决于< code>T:

int main() {
  constexpr Is_constexpr is_c;
  constexpr Not_constexpr not_c;

  std::integral_constant<int, foo(is_c)> a; // OK
  //std::integral_constant<int, foo(not_c)> a; // ERROR

}

 类似资料:
  • 当您尝试在中使用Constexpr时,如下所示: gcc和clang投诉: 错误:无法将::main声明为内联 错误:'main'不允许声明为constexr 让我们看看constexpr函数的要求是什么: constexpr函数必须满足以下要求: 它必须不是虚拟的 它的返回类型必须是文学类型 它的每个参数都必须是文字类型 什么是文学类型? 文字类型是以下任一类型 void(自c 14起) 功能体

  • 在C中,您可以将许多内容声明为:变量、函数(包括成员函数和运算符)、构造函数,以及从C 1z开始的语句和lambda表达式。但是,声明析构函数会导致错误: 我的问题: 为什么不能将析构函数标记为constexpr 如果我没有提供析构函数,那么隐式生成的析构函数是constexpr吗 如果我声明一个默认析构函数(

  • 这是有效代码: 但是在这里,我真的很想声明和 。但是为什么呢?

  • 变量被编译器声明为const,导致无法编译

  • C++1z将引入“constexpr if”-一个将根据条件删除其中一个分支的if。似乎合理有用。 但是,没有constexpr关键字就不行吗?我认为在编译过程中,编译器应该知道编译时是否知道条件。如果是的话,即使是最基本的优化级别也应该移除不必要的分支。 例如(参见godbolt:https://godbolt.org/g/ipy5y5): Godbolt explorer显示,即使带有-o0的

  • 以下代码使用Clang(3.9.1测试)和GCC(6.3测试)编译,如此链接所示:https://godbolt.org/g/kO1nBa.但是,MSVC(19.00.24215.1测试)无法编译它: 错误C2131:表达式未计算为常量 注意:失败是由赋值操作的计算引起的 注意:在评估'ExtraResidentsValueWitnessTable::ExtraResidentsValueWitn