Почему эта вложенная лямбда не считается 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;
  };
};

или что-нибудь подобное.

Другие вопросы по тегам