当给出每个信息时,为什么编译器在编译期间不评估constexpr函数?

xiv*_*r77 3 c++ constexpr c++14

这个

int main()
{
  std::cout << range(1, 11).reverse().sort().sum() << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

就是所有内容main,正如代码所说,它创建了一个从1到10(包括1和10)的列表,将其反转,通过排序取消反转,并计算总和.因此输出应为55.

代码是一个实验,(ab)使用constexprC++ 14中的宽松要求.我尽力创建一个编译时列表类,但实际上还远远不够.这个类是不完整的,但它仍然可以模仿很多功能风格的编程.

据我所知,需要constexpr让编译器在编译时评估一些东西.所以我认为编译器可以简单地用代码55替换所有代码,但事实并非如此.代码确实具有在编译时获得结果所需的一切.我错过了什么?

从评论中,我试图通过使用结果来检查问题static_assert.clang和gcc都给了我一个错误,但我也没理解,而后者似乎被打破了......

a.cpp:142:17: error: static_assert expression is not an integral constant expression
  static_assert(range(1, 11).reverse().sort().sum() == 55, "");
                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
a.cpp:60:23: note: assignment to object outside its lifetime is not allowed in a constant
      expression
  l.array[l.length++] = t;
                      ^
a.cpp:134:9: note: in call to '&l->add(1)'
    l = l.add(a);
        ^
a.cpp:142:17: note: in call to 'range(1, 11, 1)'
  static_assert(range(1, 11).reverse().sort().sum() == 55, "");
                ^
1 error generated.
Run Code Online (Sandbox Code Playgroud)

GCC

main.cpp: In function 'int main()':

main.cpp:142:3: error: non-constant condition for static assertion

   static_assert(range(1, 11).reverse().sort().sum() == 55, "");

   ^

main.cpp:142:38: error: 'constexpr List<T> List<T>::reverse() const [with T = int]' called in a constant expression

   static_assert(range(1, 11).reverse().sort().sum() == 55, "");

                                      ^

main.cpp:74:19: note: 'constexpr List<T> List<T>::reverse() const [with T = int]' is not usable as a constexpr function because:

 constexpr List<T> List<T>::reverse() const

                   ^

main.cpp:74:19: sorry, unimplemented: unexpected AST of kind result_decl

main.cpp:74: confused by earlier errors, bailing out
Run Code Online (Sandbox Code Playgroud)

完整代码

#include <cstdint>
#include <iostream>
#include <limits>
#include <algorithm>
#include <initializer_list>

template<typename T>
class List
{
  template<typename T2>
  friend std::ostream &operator<<(std::ostream &, const List<T2> &);
public:
  constexpr List();
  constexpr List(std::initializer_list<T>);
  constexpr T head() const;
  constexpr List<T> tail() const;
  constexpr List<T> add(T) const;
  constexpr List<T> merge(List<T>) const;
  constexpr List<T> reverse() const;
  template<typename Filter>
  constexpr List<T> filter(Filter) const;
  constexpr List<T> sort() const;
  constexpr T sum() const;
private:
  int length;
  T array[0x100];
};

template<typename T>
constexpr List<T>::List()
: length(0)
{
}

template<typename T>
constexpr List<T>::List(std::initializer_list<T> l)
: length {static_cast<int>(l.size())}
{
  std::copy(l.begin(), l.end(), array);
}

template<typename T>
constexpr T List<T>::head() const
{
  return array[0];
}

template<typename T>
constexpr List<T> List<T>::tail() const
{
  List<T> l;
  l.length = length - 1;
  std::copy_n(array + 1, l.length, l.array);
  return l;
}

template<typename T>
constexpr List<T> List<T>::add(T t) const
{
  List<T> l {*this};
  l.array[l.length++] = t;
  return l;
}

template<typename T>
constexpr List<T> List<T>::merge(List<T> l) const
{
  std::copy_backward(l.array, l.array + l.length, l.array + l.length + length);
  std::copy_n(array, length, l.array);
  l.length += length;
  return l;
}

template<typename T>
constexpr List<T> List<T>::reverse() const
{
  List<T> l;
  l.length = length;
  std::reverse_copy(array, array + length, l.array);
  return l;
}

template<typename T>
template<typename Filter>
constexpr List<T> List<T>::filter(Filter f) const
{
  List<T> l;
  for (int i {0}; i < length; ++i)
  {
    if (f(array[i]))
    {
      l = l.add(array[i]);
    }
  }
  return l;
}

template<typename T>
constexpr List<T> List<T>::sort() const
{
  if (length == 0)
  {
    return *this;
  }
  return tail().filter([&](T t) {return t < head();}).sort().add(head())
  .merge(tail().filter([&](T t) {return t >= head();}).sort());
}

template<typename T>
constexpr T List<T>::sum() const
{
  if (length == 0)
  {
    return T {};
  }
  return head() + tail().sum();
}

template<typename T>
std::ostream &operator<<(std::ostream &os, const List<T> &l)
{
  os << '{';
  for (int i {0}; i < l.length - 1; ++i)
  {
    os << l.array[i] << ", ";
  }
  return os << l.array[l.length - 1] << '}';
}

inline constexpr List<int> range(int a, int b, int c = 1)
{
  List<int> l;
  while (a < b)
  {
    l = l.add(a);
    a += c;
  }
  return l;
}

int main()
{
  static_assert(range(1, 11).reverse().sort().sum(), "");
  std::cout << range(1, 11).reverse().sort().sum() << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

eca*_*mur 8

初始错误是array在构造函数中未初始化的错误.您可以通过初始化来解决此问题:

template<typename T>
constexpr List<T>::List()
: length(0)
, array{}
{
}
Run Code Online (Sandbox Code Playgroud)

之后,您将遇到<algorithm>您使用的功能不是的问题constexpr; 您可以通过将标准中的示例定义复制到您的实现中并标记您的副本来解决此问题constexpr:

template<class BidirIt, class OutputIt>
constexpr OutputIt reverse_copy(BidirIt first, BidirIt last, OutputIt d_first)
{
    while (first != last) {
        *(d_first++) = *(--last);
    }
    return d_first;
}

// etcetera
Run Code Online (Sandbox Code Playgroud)

最后,你使用lambdas作为过滤谓词(in sort); lambdas在常数表达式中是非法的.这里的修复是手动将lambdas扩展为函数对象:

template<typename T>
constexpr List<T> List<T>::sort() const
{
  if (length == 0)
  {
    return *this;
  }
  T pivot = head();
  struct Lt { T pivot; constexpr bool operator()(T t) const { return t < pivot; } };
  struct Ge { T pivot; constexpr bool operator()(T t) const { return t >= pivot; } };
  return tail().filter(Lt{pivot}).sort().add(pivot)
    .merge(tail().filter(Ge{pivot}).sort());
}
Run Code Online (Sandbox Code Playgroud)

通过这些更改,您的代码将在clang 3.7下编译,但不是gcc 5.2.0.


gcc 5.2.0有两个错误:

首先,它不象结合递减,间接*(--last)reverse_copy; 这很容易修复:

template<class BidirIt, class OutputIt>
constexpr OutputIt reverse_copy(BidirIt first, BidirIt last, OutputIt d_first)
{
    while (first != last) {
        --last;
        *(d_first++) = *last;
    }
    return d_first;
}
Run Code Online (Sandbox Code Playgroud)

其次,它不喜欢将指针比较array; 但是,通过改变array + length来满足它&array[length].

这些更改的实例(与clang 3.7和gcc 5.2.0一起使用).


Use*_*ess 5

我尽力创建一个编译时列表类

但这与编译时列表完全不同 - 值存储在数组中,并且长度在插入期间发生变化。编译时列表可以是非类型参数的可变模板,或者类似Boost.MPL Integral Sequence Wrapper的东西,或者非类型参数的 Loki 风格的递归列表。

你尝试过的——并且 ecatmur 成功了——编写的是一个运行时列表,有时,它可以被 constexpr'd 掉。

具体来说,在您的代码中,List成员不是恒定的;constexpr-runtime-list 解决方案是避免改变成员并跳过其他一些 constexpr 环,而编译时列表解决方案是使类型的长度(和值)属性


为了帮助您开始,这里有一个俗气且不完整的可变参数列表,以及您想要的一些算法:

template <int... Values> struct VList {};

template <int X, typename Xs> struct cat;
template <int X, int... Xs> struct cat<X, VList<Xs...>> {
    using result = VList<X, Xs...>;
};
template <typename Xs, int X> struct rcat;
template <int... Xs, int X> struct rcat<VList<Xs...>, X> {
    using result = VList<Xs..., X>;
};
template <typename L> struct reverse;
template <int X> struct reverse<VList<X>> { using result = VList<X>; };
template <int X, int Y> struct reverse<VList<X, Y>> { using result = VList<Y, X>; };
template <int X, int... Xs> struct reverse<VList<X, Xs...>> {
    using result = typename rcat<typename reverse<VList<Xs...>>::result, X>::result;
};
template <int From, int To> struct range;
template <int End> struct range <End,End> { using result = VList<End>; };
template <int From, int To> struct range {
    using result = typename cat<From, typename range<From+1,To>::result>::result;
};

int main()
{
  typename range<1,11>::result l;
  typename reverse<decltype(l)>::result r;
  return sizeof(l) + sizeof(r);
}
Run Code Online (Sandbox Code Playgroud)

注意:

  • VList只是一个类型包装器,其内容并不重要。我们同样可以使用std::tuple<std::integral_constant<int, ...>...>
  • range应该断言From<=To, 可以允许跨步等等。
  • 我们真的不需要中等reverse专业化
  • cat确实应该被称为push_front什么,并且rcatpush_backcar我开始为前者绘制草图,并决定在上下文中可能会故意模糊。此外,这对于递归版本来说更有意义。
  • X,Xs...表示法基于 Haskellx:xs约定。当您将数据类型和算法转换为编译时操作时,函数式语言通常是寻找灵感的好地方。