Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generating objects of variable types from combinations of constructor arguments

Suppose I have types A, B with constructors A(int a, double b, std::string c), B(double a, int b).

I know how to define a function that instantiates either A or B via variadic templates.

Is there any way to design a function/macro/type that for a type T and a series of vectors of possibilities for T's constructor arguments it provides me with all possible objects?

For example, if I use this magical construct for <A, {2, 5, 6}, {2.44, 3.14}, {"yes", "no"}> it should provide the objects:

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

The same should work for B or any other type without having to rework the magical construct.

This is super easy in Python for example, but I don't know if it's possible in C++.

like image 290
Iosif Spulber Avatar asked Mar 17 '16 17:03

Iosif Spulber


People also ask

How constructor can be used to create objects?

In Java, a constructor is a block of codes similar to the method. It is called when an instance of the class is created. At the time of calling the constructor, memory for the object is allocated in the memory. It is a special type of method which is used to initialize the object.

Can we create object inside constructor?

When you create an object, you are creating an instance of a class, therefore "instantiating" a class. The new operator requires a single, postfix argument: a call to a constructor. The name of the constructor provides the name of the class to instantiate. The constructor initializes the new object.

Which constructor is used for initializing an object through another object?

A copy constructor is a member function that initializes an object using another object of the same class.


1 Answers

This uses std::experimental::array_view for efficiency. You can replace it with std::vector at some runtime cost, or a pair of iterators/pointers at some clarity cost.

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

using indexes = array_view<std::size_t>;

This iterates over the cross product of each element < the respective index in indexes. So {3,3,2} as is iterates over {0,0,0} then {0,0,1} all the way to {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 );
}

then we just populate our indexes:

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

Next, we can just have to take our arguments, put them in a vector, and then use the indexes to get elements from each vector and pass them to A to be constructed.

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... );
}

and bob is your uncle.

The As generated may be moved.

Doing this at compile time with compile time known arrays can also be done.

index_sequence_for and index_sequence are C++14, but easy to implement in C++11. There are many examples on stack overflow.

The above code has not been compiled.

like image 125
Yakk - Adam Nevraumont Avatar answered Oct 18 '22 22:10

Yakk - Adam Nevraumont