【问题标题】:Why does the C++ compiler makes it possible to declare a function as constexpr, which can not be constexpr?为什么 C++ 编译器可以将函数声明为 constexpr,而不能是 constexpr?
【发布时间】:2016-12-15 11:58:51
【问题描述】:

为什么 C++ 编译器可以将函数声明为 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&lt;reduce(std::plus&lt;int&gt;(), {1, 2, 3, 4, 5, 6, 7})&gt;(); // error 即使对于 C++14 和 C++1z

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


对于某些编译器也有同样的效果 - 支持 C++14 -std=c++14:

对于所有这些情况,编译OK,直到未使用的行://print_constexpr&lt;reduce(std::plus&lt;int&gt;(), {1, 2, 3, 4, 5, 6, 7})&gt;(); // error

【问题讨论】:

  • "C++ 编译器"?哪个编译器?海合会?铛?微软?什么版本?

标签: c++ templates c++11 constexpr compile-time


【解决方案1】:

为什么 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,编译器将不允许:

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();
}

编译器允许您这样做。没有错误。为什么?因为它是一个模板。有些实例化可能是constexpr,有些不是,取决于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

}

【讨论】:

    【解决方案2】:

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

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

    看到这个问题:When does a constexpr function get evaluated at compile time?

    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);
    }
    

    同样,如您所知,std::accumulate 不是 constexpr 函数。

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

    同样,如您所知,non-type template arguments must be constant expressions


    现在:

    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 函数的实例化模板特化 类模板的模板或成员函数将无法满足 requirements for a constexpr function 或 constexpr 构造函数, 该专业化仍然是constexpr 函数constexpr 构造函数,即使对这样的函数的调用不能出现在 一个常量表达式 ...

    注意:that

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


    当它不是模板时,它会失败:

    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的函数中调用非constexpr函数

    【讨论】:

      【解决方案3】:

      如果你写了这段代码:

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

      你会看到 reduce 不会产生 constexpr 结果。

      原因是因为 “注意:非 constexpr 函数 'accumulate >' 不能在常量表达式中使用” 正如你在这里看到的 - http://en.cppreference.com/w/cpp/algorithm/accumulate

      std::accumulate 不是 constexpr

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

      【讨论】:

      • 谢谢!是的,我知道。但是为什么 C++ 编译器可以将函数声明为 constexpr reduce() - 如果它永远不能是 constexpr?
      • 它在使用时被编译,它不会也无法尝试解析函数,直到它编译模板的特定版本。当它到达用法并触发编译时,它会解析累积并看到它不是 constexpr,因此会发出错误。
      • @TheSombreroKid:这是关键点 - 也许您应该将其添加到您的回复中。
      • @Alex 换句话说,错误不是在声明模板时发生,而是在使用特定类型实例化时发生。 std::accumulate 和 reduce 都可以为所有使用的类型提供 constexpr 特化。这些特化可能出现在模板声明之后,即编译器可能没有看到这些。
      猜你喜欢
      • 1970-01-01
      • 2011-07-03
      • 1970-01-01
      • 1970-01-01
      • 2022-09-23
      • 2016-11-01
      • 2016-11-19
      • 2020-12-03
      • 1970-01-01
      相关资源
      最近更新 更多