Шаблон функции принимает и возвращает разные лямбды - PullRequest
7 голосов
/ 03 августа 2020

У меня есть функция GetThing следующим образом:

auto GetThing(size_t index, auto&& l1)
{
    return l1;
}
auto GetThing(size_t index, auto&& l1, auto&&... rest)
{
    if (index == 0)
        return l1;
    return GetThing(index - 1, rest...);
}

Я хочу, чтобы она также могла работать с разными лямбдами , имея при этом возможность обрабатывать другие типы (то есть не лямбды, не функции, такие как int и ...) , например

std::cout << GetThing(1, 2, 3, 4);   //works, return 3
std::cout << GetThing(1, [] {return 0; }, 
    [] {return 1; }, [] {return 2; }, 
    [] {return 3; } )();             //nope

Но проблема здесь в том, что лямбды имеют другой тип, поэтому рекурсивная функция будет выведена на несовместимый тип возврата, поэтому мне кажется, что я должен использовать std::function вот так, но это некрасиво.

std::cout << GetThing(1, std::function{ [] {return 0; } }, std::function{ [] {return 1; } }, std::function{ [] {return 2; } }, std::function{ [] {return 3; } })();//works

Любой способ обойти это, например, если есть перегруженный operator(), тогда он автоматически применяет тип быть std::function?

РЕДАКТИРОВАТЬ: Я знаю, что лямбда-выражения без захвата могут быть преобразованы в указатель функции, но как это сделать без std::decay в шаблоне? Поскольку я все еще хочу обрабатывать другие типы как ссылки

EDIT2: я получаю несколько ответов, использующих std::variant, и думаю о том, что, помимо лямбда, типы параметров должны быть такими же, например. std::variant<int, int, int>. Возможно, можно добавить перегрузку к GetThing, так что, когда std::variant содержит те же типы, он возвращает объект этого типа, в противном случае (что является случаем получения лямбда-выражений) возвращает std::function

Ответы [ 4 ]

3 голосов
/ 03 августа 2020

Вы можете хранить свои функции в множестве вариантов. Конечно, это связано с некоторыми накладными расходами. Но это позволяет иметь функции также с использованием захваченных переменных.

Это позволяет выбрать функцию из такой коллекции функций и выполнить ее с заданными параметрами следующим образом:

template < typename ARR_T >
struct Collect
{
    template < typename ... T > 
    Collect( T&&...args  ): arr{std::forward<T>(args)...}{}
    ARR_T arr;
    using VARIANT_T = ARR_T::value_type;
    VARIANT_T& operator[]( size_t index) { return arr[index]; }
};

template < typename ... T > 
Collect( T&& ... args ) -> Collect< std::array< std::variant<T... >, sizeof...(T) >>; 

template < typename C, typename ... PARMS >
auto GetThing( size_t index, C&& c, PARMS&&... parms ) 
{
    return std::visit( [ &parms...]( auto&& func)
                      {
                          return func(std::forward<PARMS>(parms)...);
                      }, c[index]);
}

int main()
{
    std::cout << GetThing( 2, Collect(  []( int, double) {return 0; }, []( int, double) {return 1; }, []( int, double) {return 2; }, []( int, double) {return 3; }), 1,5.6)<< std::endl;

    int y = 8;
    double d = 9.99;

    std::cout << GetThing( 0, Collect(  [y,d]( int, double) {return d*y; }, []( int, double) {return 1.; }, []( int, double) {return 2.; }, []( int, double) {return 3.; }), 1,5.6)<< std::endl;
}



В данном случае GetThing также принимают параметры функции для вызова лямбды, потому что в вызове используется std::visit. Если вы «только» хотите выбрать функцию, вы получите std::variant, если хотите, и сможете вызвать функцию самостоятельно.


    auto func = Collect(  []( int i, double d) {return d+i; }, []( int i, double d) {return d*i; }, []( int i, double d) {return d-i; } )[2];
    std::cout << std::visit( []( auto&& f) { return f( 9, 7.77 ); }, func ) << std::endl;
}
3 голосов
/ 04 августа 2020

Вы можете вернуть std::variant, который содержит все типы ввода:

template <typename... Args>
std::variant<std::decay_t<Args>...>
GetThing(std::size_t index, Args&&... args)
{ 
  return [index, t=std::forward_as_tuple(std::forward<Args>(args)...)] 
    <std::size_t... Is>(std::index_sequence<Is...>) { 
    return std::array{ +[](const std::tuple<Args&&...>& t) { 
      return std::variant<std::decay_t<Args>...>{ 
        std::in_place_index<Is>, std::get<Is>(t)}; 
      } ... 
    }[index](t); 
  }(std::index_sequence_for<Args...>{}); 
}

Затем вам нужно std::visit, чтобы просмотреть возвращаемое значение:

for (std::size_t index = 0; index < 4; index++)
  std::visit(
    [](auto&& f) { std::cout << f() << " "; }, 
    GetThing(index, []{return 0;}, []{return 1;}, []{return 2;}, []{return 3;})
  );
2 голосов
/ 04 августа 2020

Как было решено с помощью @ 康 桓 瑋, вариант - это то, что вы хотите, когда у вас есть тип суммы.

Вот то, что я считаю более чистым решением.

template<std::size_t...Is>
using enumeration = std::variant<std::integral_constant<std::size_t, Is>...>;

An enumeration - это вариант значений времени компиляции.

Они представляют собой хранилище времени выполнения для значения времени компиляции, которое затем можно использовать.

template<class T>
struct enum_from_seq;
template<class T>
using enum_from_seq_t = typename enum_from_seq<T>::type;
template<std::size_t...Is>
struct enum_from_seq<std::index_sequence<Is...>>{ using type=enumeration<Is...>; };

template<std::size_t N>
using variant_index_t = enum_from_seq_t<std::make_index_sequence<N>>;

A variant_index_t - это enumeration из 0 к N-1. Его можно использовать для взаимодействия с вариантами.

template<std::size_t N, std::size_t...Is>
variant_index_t<N> make_variant_index_helper( std::size_t I, std::index_sequence<Is...> ) {
  constexpr variant_index_t<N> retvals[] = {
    variant_index_t<N>{ std::integral_constant<std::size_t, Is>{} }...
  };
  return retvals[I];
}

template<std::size_t N>
variant_index_t<N> make_variant_index( std::size_t I ) {
  return make_variant_index_helper<N>( I, std::make_index_sequence<N>{} );
}
//TODO: handle valueless_by_exception:
template<class...Ts>
variant_index_t<sizeof...(Ts)> get_index( std::variant<Ts...>const& v ) {
  return make_variant_index<sizeof...(Ts)>( v.index() );
}

теперь мы можем сделать enumeration для варианта из размера и элемента времени выполнения.

Это раздражает механизм, но, по крайней мере, это общий c.

template<class...Ts>
std::variant< std::decay_t<Ts>... > pick( std::size_t I, Ts&&...ts ) {
  using return_type = std::variant< std::decay_t<Ts>... >;

  std::tuple< std::remove_reference_t<Ts>*... > retvals = {std::addressof(ts)...};

  auto index = make_variant_index<sizeof...(Ts)>(I);
        
  return std::visit( [&](auto I)->return_type {
    //TODO: perfect forward here.  If constexpr on the Ith Ts maybe?
    return return_type(std::in_place_index<I>, *std::get<I>(retvals));
  }, index );
}

видите, без рекурсии.

Тестовый код:

for (std::size_t i = 0; i < 5; ++i)
{
    auto x = pick( i, 3.14, 2, "hello", "goodbye", -3.14 );
    std::visit( [](auto&& x) {
        std::cout << x << "\n";
    }, x );
}

Живой пример .

Вывод:

3.14
2
hello
goodbye
-3.14
2 голосов
/ 03 августа 2020

Для лямбда-выражений без захвата вы можете использовать указатели на функции

#include <utility> // std::forward

auto GetThing(size_t index, auto&& l1)
{
    return  std::forward<decltype(l1)>(l1);
}

auto GetThing(size_t index, auto&& l1, auto&&... rest)
{
    if (index == 0)
        return std::forward<decltype(l1)>(l1);
    return GetThing(index - 1, std::forward<decltype(rest)>(rest)...);
}

std::cout << GetThing(1,
+[] {return 0; }, +[] {return 1; }, 
+[] {return 2; }, +[] {return 3; } 
)();// works now

будет работать: Demo

Также обратите внимание, что вам нужно добавить + для преобразования лямбда-указатель на функцию. Подробнее: Положительная лямбда: '+ [] {}' - Что это за колдовство?

Также, в случае лямбды с захватом нужно использовать std::function. Вышеуказанного будет недостаточно / не подойдет!

...