Как передать функцию в качестве параметра шаблона в контейнер, который не хранит элементы, а вычисляет их на лету?

Цель

Нужен контейнер, который будет обладать следующими свойствами:

  • Не хранит ни один из элементов, вместо этого он вычисляет значение каждого элемента 'на лету', по мере надобности

  • Как почти в любом контейнере есть begin() и end().

  • Хранит только количество элементов. То есть сами элементы он не хранит, он должен вычислять их на лету.

    template <class Func, size_t N>
    struct PseudoContainer{
      size_t size = N;
    
      ... operator[](size_t n){
        return Func(n);
      }
    
      ... begin(){
      }
    
      ... end(){
      }
    };
    

Задумка у этого контейнера такая:

Cоздаём контейнер, который будет 'хранить' пять экземпляров некоторого класса

struct some_struct {
  some_struct(size_t n) : pow(n*n){
  }

  size_t pow;
};

PseudeContainer</*Сюда нужна как-то передать функцию*/, 5> c;

Теперь должна быть возможность получать значения

auto a = c.begin();  // эквивалентно some_struct(0);
auto b = c[0];  // эквивалентно some_struct(0);
auto c = c[1];  // эквивалентно some_struct(1);

for(auto el: c){
  // что-то делаем с элементом
}

В качестве функции может передаваться как класс (как в примере выше), так и функция, например

int foo(int n){
  return n*n;
}

PseudeContainer<foo, 5> c;

auto a = c.begin();  // эквивалентно some_struct(0);
auto b = c[0];  // эквивалентно some_struct(0);
auto c = c[1];  // эквивалентно some_struct(1);

Собственно вопросы

  1. Как передать в параметр шаблона функцию?
  2. Как определить тип возвращаемого значения?
  3. Как правильно реализовать begin() и end()? Нужно написать итератор, который будет хранить значение n внутри себя?
  4. Может есть что-то подобное в boost или других библиотеках?

Моя попытка выглядит так

struct some_struct {
  some_struct(size_t n) : pow(n*n){
  }

  size_t pow;
};

template <class Func, size_t Size>
struct PseudoContainer {
  using value_type = decltype(STD declval<Func(0)>()); // Тут неправильно
  using reference = value_type&;

  reference operator[](size_type pos) noexcept {
  return Func(pos);
}      


  size_t size = Size;
};


int main(){
  PseudeContainer<some_struct, 5> c; // ничего не работает
  auto _0 = c.begin();
  auto _1 = c[1];
  
  for(auto el: c){
    auto _n = el;
  }
}

Ответы (1 шт):

Автор решения: KoVadim

Я вот набросал такой код. Он может чуть странно выглядит, но как рабочий пример подойдет.

#include <iostream>
#include <stdexcept>

struct foo
{
    int operator()(int n) { return n*n;}
};

template <typename D, size_t S>
class PseudeContainer
{
    static D m_f;

public:
    // если мы хотим begin/end, то хорошо бы итератор сделать
    class my_iterator
    {
        size_t m_pos;
    public:
        explicit my_iterator(size_t position = 0) : m_pos{position} {}

        // при разыменовании - вызываем функцию от текущей позиции
        int operator*() { return m_f(m_pos);}
        // при переходе вперед, просто увеличиваем содержимое итератора
        my_iterator& operator++() {
            ++m_pos;
            return *this;
        }
        // итератор должен знать, когда останавливаться
        bool operator!=(const my_iterator& other) {
            return m_pos != other.m_pos;
        }
    };

    
    const auto operator[](size_t index) {
        if (index > S) throw std::out_of_range("bad index");
        return m_f(index);
    }
    my_iterator begin() { return my_iterator(0);}
    my_iterator end() { return my_iterator(S-1);}
};

int main()
{
    PseudeContainer<foo, 5> c;

    auto a = c.begin();  // эквивалентно some_struct(0);
    auto b = c[0];  // эквивалентно some_struct(0);
    auto z = c[1];  // эквивалентно some_struct(1);
    std::cout << z << '\n';
    for (auto a : c) {
        std::cout << a << '\n';
    }
}

Теперь ответы

Как передать в параметр шаблона функцию?

если посмотреть на stl, то каждый раз, когда нужно передать функцию как параметр шаблона, там передается функтор. Собственно я это и сделал.

Как определить тип возвращаемого значения?

Можно посмотреть, как это делают люди https://stackoverflow.com/questions/41301536/get-function-return-type-in-template или посмотреть на встроенный result_of / invoke_of, но в Notes какой то треш, первая функция была задеприкейчена...

Но есть обходной путь, который просто все решает - auto. Если придираться к стандарту, то это с 14 можно только делать, но большинство компиляторов умеет и с 11. И мне этот способ очень нравится. Компилятор сам быстро и красиво разберется с типом, чем разворачивать десятки хитрых шаблонов.

Как правильно реализовать begin() и end()? Нужно написать итератор, который будет хранить значение n внутри себя?

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

Может есть что-то подобное в boost или других библиотеках?

Возможно. Буст он такой. Но из за того, что он такой большой, часто легче написать свое, чем тащить его за собой.

→ Ссылка