Подтвердить что ты не робот

Создание объектов переменных типов из сочетаний аргументов конструктора

Предположим, что у меня есть типы A, B с конструкторами A(int a, double b, std::string c), B(double a, int b).

Я знаю, как определить функцию, которая создает экземпляры A или B через вариативные шаблоны.

Есть ли способ создать функцию/макро/тип, которая для типа T и ряд векторов возможностей для аргументов конструктора T предоставляет мне все возможные объекты?

Например, если я использую эту магическую конструкцию для <A, {2, 5, 6}, {2.44, 3.14}, {"yes", "no"}>, она должна предоставить объекты:

A(2, 2.44, "yes")
A(2, 2.44, "no")
A(2, 3.14, "yes")
...
A(6, 3.14, "no")

То же самое должно работать для B или любого другого типа без необходимости переделывать магическую конструкцию.

Это очень просто в Python, но я не знаю, возможно ли это на С++.

4b9b3361

Ответ 1

Для эффективности используется std::experimental::array_view. Вы можете заменить его на std::vector при некоторой стоимости исполнения или на пару итераторов/указателей с некоторой ясностью.

template<class T>
using array_view = std::experimental::array_view<T>;

using indexes = array_view<std::size_t>;

Это выполняет итерацию над поперечным произведением каждого элемента < соответствующего индекса в индексах. Итак {3,3,2} как is выполняет итерацию над {0,0,0}, затем {0,0,1} до {2,2,1}.

template<class F>
void for_each_cartesian_product(
  indexes is,
  F&& f,
  std::vector<std::size_t>& result
) {
  if (is.empty()) {
    f(result);
    return;
  }
  auto max_index = is.front();
  for (std::size_t i = 0; i < max_index; ++i) {
    result.push_back(i);
    for_each_cartesian_product( {is.begin()+1, is.end()}, f, result );
    result.pop_back();
  }
}
template<class F>
void for_each_cartesian_product(
  indexes is,
  F&& f
) {
  std::vector<size_t> buffer;
  for_each_cartesian_product( is, f, buffer );
}

то мы просто заполняем наши индексы:

template<class...Ts>
std::vector<std::size_t> get_indexes( std::vector<Ts> const&... vs ) {
  return {vs.size()...};
}

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

template<class T, std::size_t...Is, class...Args>
std::vector<T> make_stuff( std::index_sequence<Is...>, std::vector<Args>const&... args ) {
  std::vector<T> retval;
  for_each_cartesian_product(
    get_indexes(args...),
    [&](auto&& index){
      retval.emplace_back( args[ index[Is] ]... );
    }
  );
  return retval;
}
template<class T, class...Args>
std::vector<T> make_stuff( std::vector<Args>const&... args ) {
  return make_stuff<T>( std::index_sequence_for<Args...>{}, args... );
}

и Боб - твой дядя.

Сгенерированный A может быть перемещен.

Выполнение этого во время компиляции с известными массивами времени компиляции также может быть выполнено.

index_sequence_for и index_sequence являются С++ 14, но их легко реализовать на С++ 11. Существует много примеров.

Вышеприведенный код не был скомпилирован.

Ответ 2

Вот относительно простой подход:

#include <vector>
#include <string>
#include <iostream>
#include <tuple>

using std::vector;
using std::cout;
using std::tie;


template <size_t index,size_t count>
struct Maker {
  template <typename T,typename Tuple,typename...Args>
  static void make(vector<T> &v,const Tuple &tup,Args &...args)
  {
    for (auto &x : std::get<index>(tup)) {
      Maker<index+1,count>::make(v,tup,args...,x);
    }
  }
};

template<size_t index>
struct Maker<index,index> {
  template <typename T,typename Tuple,typename...Args>
  static void make(vector<T> &v,const Tuple &,Args &...args)
  {
    v.push_back(T(args...));
  }
};

template <typename T,typename...Ts>
static vector<T> combinations(const Ts &...args)
{
  vector<T> v;
  Maker<0,sizeof...(args)>::make(v,tie(args...));
  return v;
}




int main()
{
  struct A {
    A(int,double,std::string) { }
  };

  struct B {
    B(double,int) { }
  };

  vector<A> as =
    combinations<A>(
      vector<int>{2,5,6},
      vector<double>{2.44,3.14},
      vector<const char *>{"yes","no"}
    );
  vector<B> bs =
    combinations<B>(
      vector<double>{2.44,3.14},
      vector<int>{2,5,6}
    );
  cout << "as.size()=" << as.size() << "\n";
  cout << "bs.size()=" << bs.size() << "\n";
}

Вывод:

12
6

Ответ 3

Мое решение не так элегантно, как у Yacc (ведь я пытаюсь учиться), но я думаю, что это относительно просто.

Комментарии и сообщения о дефектах приветствуются.

#include <tuple>
#include <vector> 
#include <complex>
#include <iostream>
#include <initializer_list>


template <std::size_t ... N, typename X, typename ... Al>
void cartHelp (std::vector<X> & v,
               std::tuple<Al...> const & t)
 { v.emplace_back(std::get<N>(t)...); }


template <std::size_t ... N, typename X, typename ... Al1, typename L,
   typename ... Al2>
void cartHelp (std::vector<X> & v,
               std::tuple<Al1...> const & t, 
               std::initializer_list<L> const & l,
               std::initializer_list<Al2> const & ... al2)
 {
   for ( auto const & elem : l )
      cartHelp<N..., sizeof...(N)>(v, std::tuple_cat(t, std::tie(elem)),
                                   al2...);
 }



template <typename X, typename L, typename ... Al>
std::vector<X> cartesian (std::initializer_list<L> const & l,
                          std::initializer_list<Al> const & ... al)
 {
   std::vector<X>  v;

   for ( auto const & elem : l )
      cartHelp<0>(v, std::tie(elem), al...);

   return v;
 }


int main()
 {
   auto v1 = cartesian<int>({1,2});

   std::cout << "--- v1.size --- " << v1.size() << "\n";
   std::cout << "v1";
   for ( auto const & elem : v1 )
      std::cout << '[' << elem << ']';
   std::cout << '\n';

   auto v2 = cartesian<std::complex<double>>
      ({1.2,2.3,3.4}, {11.11, 22.22, 33.33});

   std::cout << "--- v2.size --- " << v2.size() << "\n";
   std::cout << "v2";
   for ( auto const & elem : v2 )
      std::cout << '[' << elem << ']';
   std::cout << '\n';

   auto v3 = cartesian<std::tuple<int, double, std::string>>
      ({1, 2, 3, 4, 5}, {0.1, 0.2, 0.3, 0.4},
       {std::string("aaa"), std::string("bbb"), std::string("ccc")});

   std::cout << "--- v3.size --- " << v3.size() << "\n";
   std::cout << "v3";
   for ( auto const & elem : v3 )
      std::cout << '[' << std::get<0>(elem) << ',' << std::get<1>(elem)
         << ',' << std::get<2>(elem) << ']';
   std::cout << '\n';

   return 0;
 }

Вывод:

--- v1.size --- 2
v1[1][2]
--- v2.size --- 9
v2[(1.2,11.11)][(1.2,22.22)][(1.2,33.33)][(2.3,11.11)][(2.3,22.22)][(2.3,33.33)][(3.4,11.11)][(3.4,22.22)][(3.4,33.33)]
--- v3.size --- 60
v3[1,0.1,aaa][1,0.1,bbb][1,0.1,ccc][1,0.2,aaa][1,0.2,bbb][1,0.2,ccc][1,0.3,aaa][1,0.3,bbb][1,0.3,ccc][1,0.4,aaa][1,0.4,bbb][1,0.4,ccc][2,0.1,aaa][2,0.1,bbb][2,0.1,ccc][2,0.2,aaa][2,0.2,bbb][2,0.2,ccc][2,0.3,aaa][2,0.3,bbb][2,0.3,ccc][2,0.4,aaa][2,0.4,bbb][2,0.4,ccc][3,0.1,aaa][3,0.1,bbb][3,0.1,ccc][3,0.2,aaa][3,0.2,bbb][3,0.2,ccc][3,0.3,aaa][3,0.3,bbb][3,0.3,ccc][3,0.4,aaa][3,0.4,bbb][3,0.4,ccc][4,0.1,aaa][4,0.1,bbb][4,0.1,ccc][4,0.2,aaa][4,0.2,bbb][4,0.2,ccc][4,0.3,aaa][4,0.3,bbb][4,0.3,ccc][4,0.4,aaa][4,0.4,bbb][4,0.4,ccc][5,0.1,aaa][5,0.1,bbb][5,0.1,ccc][5,0.2,aaa][5,0.2,bbb][5,0.2,ccc][5,0.3,aaa][5,0.3,bbb][5,0.3,ccc][5,0.4,aaa][5,0.4,bbb][5,0.4,ccc]

Ответ 4

Вот версия версии Vaughn Cato на С++ 14 (кредит там, где она должна), которая может принимать как однородные контейнеры ( те, которые содержат все объекты одного типа) и гетерогенные std::tuple контейнеры (которые могут содержать объекты различного типа) в качестве входных данных. Это позволяет вам смешивать и сопоставлять типы параметров для вызова нескольких конструкторов.

Это можно было бы реализовать на С++ 11, но потребовалось бы немного больше кода для эмуляции общего лямбда-захвата вариативных аргументов.

#include <algorithm>
#include <tuple>
#include <vector>

template <typename Ts, size_t... Is, typename F>
void for_each_impl(Ts const& t, std::index_sequence<Is...> is, F const& f)
{
    using expand = int[];
    expand{ (f(std::get<Is>(t)), 0)... };
}

template <typename... Ts, typename F>
void for_each(std::tuple<Ts...> const& t, F const& f)
{
    for_each_impl(t, std::make_index_sequence<sizeof...(Ts)>(), f);
}

template <typename T, typename F>
void for_each(T const& t, F const& f)
{
    std::for_each(std::begin(t), std::end(t), f);
}

template <std::size_t Index, std::size_t Count>
struct cartesian_builder
{
    template <typename T, typename Sets, typename... Args>
    static void make(std::vector<T> &v, Sets const& sets, Args const&... args)
    {
        for_each(std::get<Index>(sets), [&](auto& arg) {
            cartesian_builder<Index + 1, Count>::make(v, sets, args..., arg);
        });
    }
};

template <std::size_t Count>
struct cartesian_builder<Count, Count> {
    template <typename T, typename Sets, typename... Args>
    static void make(std::vector<T>& v, Sets const&, Args const&... args)
    {
        v.emplace_back(args...);
    }
};

template <>
struct cartesian_builder<0, 0> {
    template <typename T, typename Sets, typename... Args>
    static void make(std::vector<T>&, Sets const&, Args const&...)
    {
    }
};

template <typename T, typename... Sets>
static std::vector<T> make_cartesian_product(Sets const&... sets)
{
    std::vector<T> v;
    cartesian_builder<0, sizeof...(sets)>::make(v, std::tie(sets...));
    return v;
}

Пример использования:

#include <iostream>
#include <set>
#include <string>
#include <vector>

int main()
{
    struct A
    {
        int a;
        double b;
        std::string c;

        A(std::string a, double b, std::string c) :
            a(0), b(b), c(a + " " + c)
        {
        }

        A(int a, double b, std::string c) :
            a(a), b(b), c(c)
        {
        }
    };

    std::vector<A> objects = make_cartesian_product<A>(
        std::make_tuple(2, "maybe", 6),
        std::set<double>{2.44, 3.14},
        std::vector<char const*>{"yes", "no"}
    );

    for (auto& o : objects)
    {
        std::cout << "(" << o.a << ", " << o.b << ", " << o.c << ")\n";
    }
}

Ответ 5

То, что вы ищете, это "factory", который может или не может вызывать один или несколько конструкторов в зависимости от вашей реализации. Конечно, возможно, если A и B произошли от одного и того же родительского класса (или если один из них является потомком другого).

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

Я хотел бы быть более конкретным, но мне нужно будет увидеть хотя бы образец кода Python, на который вы ссылаетесь.

Ответ 6

auto variadic lambdas (С++ 14) может использоваться для хранения ссылок аргументов вместо промежуточных индексов.

Код становится очень простым, не уверен в производительности:

/** calls f for each element in c */
template <typename F, typename C>
void for_each_product(F&& f, const C& c) {
  for (auto& e: c) f(e);
}

/** calls f for each cartesian product from c,cs containers */
template <typename F, typename C, typename... Cs>
void for_each_product(F&& f, const C& c, const Cs&... cs) {
  for (auto& e: c)
    for_each_product(
             [&f, &e](auto&&... args) {
               f(e, args...);
             }, cs...);
}

Вот и все: -)

Вот как это использовать:

#include <vector>
#include <string>

struct A {
  int i;
  double d;
  std::string s;
  A(int _i, double _d, std::string _s): i(_i), d(_d), s(_s) {}
};

int main() {
  auto vi = std::vector<int>{2, 5, 6};
  auto vd = std::vector<double>{2.44, 3.14};
  auto vs = std::vector<std::string>{"yes", "no"};
  auto va = std::vector<A>();

  for_each_product([&va](auto&&... args){
      va.emplace_back(args...);
    }, vi, vd, vs);
}

Дополнительная поддержка списков инициализаторов:

/** calls f for each element in c */
template <typename F, typename T>
void for_each_product(F&& f, const std::initializer_list<T>& c){
  for (auto& e: c) f(e);
}

/** calls f for each cartesian product from c,cs containers */
template <typename F, typename T, typename... Ts>
void for_each_product(F&& f, const std::initializer_list<T>& c,
              const std::initializer_list<Ts>&... cs){
  for (auto& e: c)
    for_each_product(
             [&f,&e](auto&&... args) {
               f(e, args...);
             }, cs...);
}

Тогда можно использовать его следующим образом:

 for_each_product([&va](auto&&... args){ va.emplace_back(args...); },
    {2, 5, 6},
    {2.44, 3.14},
    {"yes", "no"});

Однако списки инициализаторов не смешиваются с контейнерами, но не уверены, как это исправить.