C ++, создающий лямбда из других лямбда-выражений constexpr, выполняемых по порядку, не может быть constexpr - PullRequest
2 голосов
/ 10 июля 2020

Допустим, я хочу создать лямбду, которая выполняет некоторые другие лямбды в следующем порядке:

constexpr auto some_func{[]() {
  // do something
}};

constexpr auto some_other_func{[]() {
  // do something else
}};

constexpr auto funcs_tuple = std::tuple(some_func, some_other_func);
constexpr auto combined_funcs = do_funcs(funcs_tuple);

combined_funcs();

Я реализовал функцию do_funcs как:

template <std::size_t Idx = 0, typename Tuple>
constexpr auto do_funcs(const Tuple& tup) {
  return [&]() {
    if constexpr (Idx < std::tuple_size_v<Tuple>) {
      const auto f = std::get<Idx>(tup);
      f();
      do_funcs<Idx + 1>(tup)();
    }
  };
}

, который просто выполняет функции в кортеже по порядку. Однако результирующая переменная combined_funcs не может быть объявлена ​​constexpr, потому что ссылка на funcs_tuple в вызове do_funcs не является постоянным выражением.

Я пробую код в Compiler Explorer с лязгом (стволом) и получаем

error: constexpr variable 'combined_funcs' must be initialized by a constant expression

note: reference to 'funcs_tuple' is not a constant expression

Почему это не так считать постоянным выражением? Есть ли способ сделать это constexpr?

После некоторых проб и ошибок я обнаружил, что вместо захвата кортежа по ссылке в возвращенной лямбде из do_funcs, а в результате захвата по значению полученная лямбда может действительно быть объявленным constexpr, но я действительно не хочу создавать копию кортежа для каждого рекурсивного вызова do_funcs.

constexpr auto do_funcs(const Tuple& tup) {
  // capture by value instead of reference
  //      |
  //      v
  return [=]() { ...

Я также хотел бы создать вспомогательную функцию, которая принимает пакет параметров лямбда-выражений и отправляет его как кортеж в функцию do_funcs следующим образом:

template <typename... Funcs>
constexpr auto make_do_funcs(Funcs&&... fs) {
  const auto funcs = std::tuple(std::forward<Funcs>(fs)...);
  return do_funcs(funcs);
}

Причина, по которой я использую кортежи вместо другого такого метода:

template <typename Func, typename... Funcs>
constexpr auto do_funcs(Func&& f, Funcs&&... fs) {
  return [f = std::forward<Func>(f), ... fs = std::forward<Funcs>(fs)] {
    f();
    if constexpr (sizeof...(fs) > 0) {
      do_funcs(fs...);
    }
  };
}

, заключается в том, что «идеальный захват» - это функция C ++ 20 и требует обходного пути с кортежами для C ++ 17.

Для получения дополнительной информации я пытаюсь создать служебную «парсер» для моей библиотеки комбинированного анализатора, которая выполняет некоторые другие парсеры для создания более сложного парсера, когда это необходимо.

Ответы [ 3 ]

2 голосов
/ 10 июля 2020

А как насчет простого std::apply() (который складывается над оператором запятой) следующим образом?

#include <iostream>
#include <tuple>

int main()
 {
   auto some_func_1{[]() { std::cout << "1" << std::endl; }};
   auto some_func_2{[]() { std::cout << "2" << std::endl; }};

   auto funcs_tuple = std::tuple{some_func_1, some_func_2};

   auto do_funcs
      = [](auto const & tpl)
         { std::apply([](auto ... fn){ (fn(), ...); }, tpl); };

   do_funcs(funcs_tuple);
 }

Вы можете видеть, что вы напечатали 1 и 2

Удаление std::cout s (не совместим с constexpr) и добавив constexpr, вы получите

#include <iostream>
#include <tuple>

int main()
 {
   constexpr auto some_func_1{[]() { }};
   constexpr auto some_func_2{[]() { }};

   constexpr auto funcs_tuple = std::tuple{some_func_1, some_func_2};

   constexpr auto do_funcs
      = [](auto const & tpl)
       { std::apply([](auto ... fn){ (fn(), ...); }, tpl); };

   do_funcs(funcs_tuple);
 }
1 голос
/ 11 июля 2020

Марка funcs_tuple static. Поскольку это constexpr, это не должно действительно ничего менять в том, как работает ваш код, поскольку все вызовы любой функции, которая это есть, всегда должны иметь одно и то же значение funcs_tuple. (Различия были бы, если бы вы по какой-то причине взяли его адрес.) Однако он делает ссылки на funcs_tuple constexpr, потому что теперь есть один объект, представленный constexpr переменная, а не одна на вызов функции. Godbolt

Обратите внимание, что это не работает для функции constexpr. К счастью, если закрывающая функция - constexpr, переменные не обязательны. То есть вы можете сделать оба / либо

void func() { // func not constexpr
    static constexpr auto funcs_tuple = ...;
    constexpr auto combined_funcs = do_funcs(funcs_tuple);
}

, либо

// like in the question
template <typename... Funcs>
constexpr auto make_do_funcs(Funcs&&... fs) {
    // funcs not constexpr or static; make_do_funcs still constexpr
    const auto funcs = std::tuple(std::forward<Funcs>(fs)...);
    return do_funcs(funcs)(); // or whatever
    // note: you CANNOT return do_funcs(funcs), because that would be a dangling reference
    // your original make_do_funcs is simply broken
}
0 голосов
/ 11 июля 2020

В результате я использовал свою собственную вспомогательную структуру, которая действует как лямбда.

template <typename... Funcs>
struct do_funcs {
  constexpr do_funcs(Funcs... fs) : funcs{fs...} {}

  void operator()() const {
    do_rest();
  }

  template <std::size_t Idx = 0>
  void do_rest() const {
    if constexpr (Idx < sizeof...(Funcs)) {
      const auto f = std::get<Idx>(funcs);
      f();
      do_rest<Idx + 1>();
    }
  }

  const std::tuple<Funcs...> funcs;
};

, что позволяет приведенному в вопросе примеру быть constexpr.

...