Использование лямбда-выражения вместо индексируемого параметра шаблона

#c 11 #templates #lambda

#c 11 #шаблоны #лямбда

Вопрос:

У меня есть метод, который принимает индексируемый объект в качестве параметра шаблона, что-то вроде:

 template <typename OBJ>
int foo(int n, OBJ o)
{
  int x = 0;
  for (int i = 0; i < n;   i) {
    x  = o[i];
  }
  return x;
}
  

Есть ли способ, которым я могу передать лямбда-функцию для o параметра? Другими словами, возможность вызова лямбда-выражения через [] оператор, а не () operator?

Комментарии:

1. Нет, это невозможно. Вам нужно передать объект, который реализует operator[] , а лямбда-объекты этого не делают.

Ответ №1:

 template<class F>
struct square_bracket_invoke_t {
  F f;
  template<class T>
  auto operator[](Tamp;amp; t)const
  -> typename std::result_of< F constamp;(Tamp;amp;) >::type
  { return f(std::forward<T>(t)); }
};
template<class F>
square_bracket_invoke_t< typename std::decay<F>::type >
make_square_bracket_invoke( Famp;amp; f ) {
  return {std::forward<F>(f)};
}
  

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

Код выполнен на C 11 и имеет практически нулевые накладные расходы.

 int main() {
  std::cout << foo( 6, make_square_bracket_invoke([](int x){ return x; } ) ) << "n";
}
  

результатом является 0 1 2 3 4 5 он же 15.

Хорошая ли это идея? Возможно. Но зачем на этом останавливаться?

Для максимального развлечения:

 const auto idx_is = make_square_bracket_invoke([](autoamp;amp;f){return make_square_bracket_invoke(decltype(f)(f));});
int main() {
  std::cout << foo( 6, idx_is[[](int x){ return x; }] ) << "n";
}
  

Комментарии:

1. Очень приятно. Вопрос для моего назидания. Что std::decay<F>::type делает для лямбда-выражения?

2. @RSahu Если F есть X constamp; , то оно X есть. Если это так, то Xamp; это X так. Если это так, то X это X так. За исключением, если F это функция или ссылка на функцию или массив или ссылка на тип массива. Лямбды не являются ни одним из них. decay делает тип подходящим для хранения в качестве значения; например, передавая его в функцию.

3. Спасибо за разъяснение. Есть еще много аспектов C 11, которых мне еще предстоит достичь.

Ответ №2:

Вы можете сделать это с помощью:

  1. Создание шаблона класса, функтора, который имеет operator[] определенный.
  2. Реализация operator[] в терминах operator() std::function a-ов.
  3. Сохранение лямбда-выражения в обернутом виде std::function в качестве переменной-члена шаблона класса.

Вот демонстрационная программа.

 #include <iostream>
#include <functional>

template <typename OBJ>
int foo(int n, OBJ o)
{
  int x = 0;
  for (int i = 0; i < n;   i) {
    x  = o[i];
  }
  return x;
}

template <typename> struct Functor;

template <typename R> struct Functor<R(int)>
{
   using ftype = std::function<R(int)>;
   Functor(ftype f) : f_(f) {}

   R operator[](int i) const { return f_(i); }

   ftype f_;
};

int main()
{
   Functor<int(int)> f = {[](int i) -> int {return i*i;}};
   std::cout << foo(10, f) << std::endl;
}
  

и его вывод

 285
  

Живая демонстрация

PS

Functor здесь не подходит название. Это не перегружает оператор вызова функции. Я подозреваю, что есть более подходящее имя.

Комментарии:

1. Мне не нравится передавать подпись.

Ответ №3:

Что ж, если это поможет, вот способ переслать класс-оболочку operator[] в ваш лямбда-класс operator() .

 template<class F>
struct SubscriptWrapper_t {
  F f_;
  template<class T> auto operator[](T constamp; t_) const -> decltype(f_(t_)) { 
    return f_(t_); 
  }
};
template<class F> 
SubscriptWrapper_t<typename std::decay<F>::type> SubscriptWrapper(Famp;amp; f_) {
  return{std::forward<F>(f_)}; 
}
  

Я часто использую подобные обертки. Они удобны и, похоже, не требуют каких-либо вычислительных затрат, по крайней мере, при компиляции с помощью GCC. Вы можете создать его для at или даже создать для find .

РЕДАКТИРОВАТЬ: Обновлено для C 11 (и обновлено, чтобы иметь возможность возвращать ссылку)

Комментарии:

1. не decltype(auto) в C 14? В 11, который запросил OP, это не сработает.

2. @Yakk-AdamNevraumont Мне следовало лучше прочитать теги вопросов. Сейчас он обновлен, хотя и сделан избыточным.

Ответ №4:

Набросок типа-оболочки, который мог бы это сделать.

 template<typename UnaryFunction>
class index_wrapper
{
public:
    index_wrapper(UnaryFunction func) : func(std::move(func)) {}

    template<typename T>
    std::invoke_result_t<UnaryFunction, T> operator[](Tamp;amp; t)
    { return func(std::forward<T>(t)); }

private:
    UnaryFunction func;
};
  

С использованием

 #include <iostream>

template <typename OBJ>
int foo(int n, OBJ o)
{
  int x = 0;
  for (int i = 0; i < n;   i) {
    x  = o[i];
  }
  return x;
}

int main()
{
  index_wrapper f([](int i) -> int { return i*i; });
  std::cout << foo(10, f) << std::endl;
}
  

Возможно, вам захочется ограничить его одним типом параметра, чтобы вы могли предоставлять псевдонимы типа элемента, аналогичные std::vector::reference и др.

Комментарии:

1. Это C 17 — OP запросил 11