Почему эта вложенная лямбда не считается constexpr?
Я пытаюсь создать карри интерфейс, используя вложенные лямбды constexpr, но компилятор не считает его константным выражением.
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);
}
Я отправил вопрос раньше, потому что нашел другой минимальный пример, но этого было недостаточно.
Ошибка:
test2.cpp: In instantiation of ‘<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
Если бы кто-то мог объяснить мне эту ошибку, это было бы большим подспорьем. Я столкнулся с этой ошибкой раньше, но не могу вспомнить почему.
2 ответа
Проблема в том, что вы пытаетесь использовать одну из захваченных лямбда-переменных в аргументе нетипизированного шаблона.
return hana::type_c<Array<typename decltype(type)::type, size()>>;
// ^~~~
Аргумент типа не должен быть константным выражением. Внутри лямбды вы не можете использовать захваченную переменную в константном выражении. Является ли лямбда или нет 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) в лямбда-выражении, ссылка на
this
или к переменной с автоматической продолжительностью хранения, определенной вне этого лямбда-выражения, где ссылка будет использоваться в odr.
CWG1613 может содержать некоторую подсказку.
Если бы мы переписали внутреннюю лямбду как именованный класс, у нас была бы другая, но связанная проблема:
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
не получайте особого разрешения, чтобы ослабить правила для константных выражений, которые могут появляться внутри них.
Теперь исходная ошибка имеет немного больше смысла, потому что захваченные переменные преобразуются в члены-данные типа закрытия лямбды, поэтому использование захваченных переменных немного похоже на косвенное обращение через лямбда-выражение " this
указатель".
Это обходной путь, который вносит наименьшее изменение в код:
constexpr auto array_ = [] (auto size) {
return [=] (auto type) {
const auto size_ = size;
return hana::type_c<Array<typename decltype(type)::type, size_()>>;
};
};
Теперь мы используем захваченную переменную вне константного выражения, чтобы инициализировать обычную переменную, которую мы можем затем использовать в аргументе нетипизированного шаблона.
Этот ответ редактировался несколько раз, поэтому комментарии ниже могут ссылаться на предыдущие редакции.
Я сократил ваш тестовый пример до этого:
#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
не является постоянным выражением внутри тела функции. Это не характерно для Ханы. В качестве обходного пути вы можете использовать
constexpr auto f = [](auto size) {
return [=](){
constexpr auto s = decltype(size)::value;
return 1;
};
};
или что-нибудь подобное.