7

I'm trying to create a curried interface using nested constexpr lambdas, but the compiler does not consider it to be a constant expression.

namespace hana = boost::hana;
using namespace hana::literals;

struct C1 {};

template < typename T,
           std::size_t size >
struct Array {};

constexpr auto array_ = [] (auto size) {
      return [=] (auto type) {
        return hana::type_c<Array<typename decltype(type)::type, size()>>;
      };
    };

int main() {

  constexpr auto c1 = hana::type_c<C1>;
  constexpr auto test = hana::type_c<Array<typename decltype(c1)::type, hana::size_c<100>()>>;
  constexpr auto test2 = array_(hana::size_c<100>)(c1);
}

I post a question earlier because I found a different minimal example, but it wasn't enough.

Error:

test2.cpp: In instantiation of ‘&lt;lambda(auto:1)>::<lambda(auto:2)> [with auto:2 = boost::hana::type_impl<C1>::_; auto:1 = boost::hana::integral_constant<long unsigned int, 100>]’:
test2.cpp:31:54:   required from here
test2.cpp:20:16: error: ‘__closure’ is not a constant expression
         return hana::type_c<Array<typename decltype(type)::type, size()>>;
                ^~~~
test2.cpp:20:16: note: in template argument for type ‘long unsigned int’ 
test2.cpp: In function ‘int main()’:
test2.cpp:31:18: error: ‘constexpr const void test2’ has incomplete type
   constexpr auto test2 = array_(hana::size_c<100>)(c1);

__closure is not a constant expression : if someone could explain me this error that would be a great help. I ran into that error before but can't remember why.

4

2 回答 2

6

我将您的测试用例简化为:

#include <type_traits>

constexpr auto f = [](auto size) {
  return [=](){
    constexpr auto s = size();
    return 1;
  };
};

static_assert(f(std::integral_constant<int, 100>{})(), "");

int main() { }

正如上面评论中所说,发生这种情况是因为size它不是函数体内的常量表达式。这不是 Hana 特有的。作为一种解决方法,您可以使用

constexpr auto f = [](auto size) {
  return [=](){
    constexpr auto s = decltype(size)::value;
    return 1;
  };
};

或任何类似的东西。

于 2017-04-28T13:34:48.160 回答
5

问题是您试图在模板非类型参数中使用 lambda 捕获的变量之一。

  return hana::type_c<Array<typename decltype(type)::type, size()>>;
//                                                         ^~~~

模板非类型参数必须是常量表达式。在 lambda 中,您不能在常量表达式中使用捕获的变量。lambda 是否constexpr无关紧要。

但是你可以在常量表达式中使用普通变量,即使它们不是constexpr变量。例如,这是合法的:

std::integral_constant<int, 100> i; // i is not constexpr
std::array<int, i()> a; // using i in a constant expression

那么为什么我们不能在常量表达式中使用捕获的变量呢?我不知道这条规则的动机,但它在标准中:

[expr.const]

(¶2)条件表达式核心常量表达式,除非... (¶2.11) 在lambda-expressionthis中,对具有自动存储持续时间的变量的引用或在该lambda-expression之外定义,其中引用将是一种气味用途。

CWG1613可能有一些线索。

如果我们将内部 lambda 重写为命名类,我们将遇到一个不同但相关的问题:

template <typename T>
struct Closure {
  T size;
  constexpr Closure(T size_) : size(size_) {}

  template <typename U>
  constexpr auto operator()(U type) const {
    return hana::type_c<Array<typename decltype(type)::type, size()>>;
  }
};
constexpr auto array_ = [] (auto size) {
  return Closure { size };
};

现在错误将是this在模板非类型参数中隐式使用指针。

  return hana::type_c<Array<typename decltype(type)::type, size()>>;
//                                                         ^~~~~

我声明Closure::operator()()为一个constexpr函数以保持一致性,但这并不重要。this禁止在常量表达式中使用指针([expr.const] ¶2.1)。声明的函数constexpr没有得到特殊的豁免来放宽可能出现在其中的常量表达式的规则。

现在原来的错误更有意义了,因为捕获的变量被转换为 lambda 的闭包类型的数据成员,所以使用捕获的变量有点像通过 lambda 自己的“this指针”进行间接寻址。

这是对代码进行最少更改的解决方法:

constexpr auto array_ = [] (auto size) {
  return [=] (auto type) {
    const auto size_ = size;
    return hana::type_c<Array<typename decltype(type)::type, size_()>>;
  };
};

现在我们在常量表达式之外使用捕获的变量来初始化一个普通变量,然后我们可以在模板非类型参数中使用它。

这个答案已经被编辑了几次,所以下面的评论可能会引用以前的修订。

于 2017-04-28T20:58:09.843 回答