I'm trying to make a function which will make generalized outer product of two arrays in a way that creates dynamically allocated 2D array (matrix). The types of the first two parameters must be identical to the types of the next two parameters, but the types of the first two parameters do not have to be identical to the types of the next two parameters (eg the first two parameters can be pointers to deck of integers). This also implies that the elements of these two arrays do not necessarily have to be of the same type. As for the function 𝑓, it should be a lambda function. The only limitation is that it must be able to receive elements of these two arrays as parameters. The type of value returned by the function 𝑓 can be any, and this type will be elements of the matrix.
EXAMPLE
5 2 8
1 3 6 2
Function f(x,y)=x+2y
7 11 17 9
4 8 14 6
10 14 20 12
#include <iostream>
#include <vector>
/*I don't know how to make Generalized_Outer_Product accept lambda function*/
template < typename iterator_tip1, typename iterator_tip2, typename tip >
auto Generalized_Outer_Product(iterator_tip1 start_first, iterator_tip1 after_end_first,
iterator_tip2 start_second, iterator_tip2 after_end_second, tip f(tip)) {
using type_of_object_first = typename std::decay < decltype( * start_first) > ::type;
using type_of_object_second = typename std::decay < decltype( * start_second) > ::type;
int n1 = std::distance(start_first, after_end_first);
int n2 = std::distance(start_second, after_end_second);
type_of_object_first ** mat = nullptr;
mat = new int * [n1];
for (int i = 0; i < n1; i++)
mat[i] = nullptr;
try {
for (int i = 0; i < n1; i++)
mat[i] = new type_of_object_first[n2];
for (int i = 0; i < n1; i++) {
for (int j = 0; j < n2; j++) {
mat[i][j] = f( * start_first, * start_second);
start_second++;
}
start_first++;
start_second -= n2;
}
} catch (...) {
for (int i = 0; i < n1; i++)
delete[] mat[i];
delete[] mat;
throw std::range_error("Not enough memory");
}
return mat;
}
int main() {
int n1, n2, x;
std::cin >> n1;
std::vector < int > a, b, c;
for (int i = 0; i < n1; i++) {
std::cin >> x;
a.push_back(x);
}
std::cin >> n2;
for (int i = 0; i < n2; i++) {
std::cin >> x;
b.push_back(x);
}
try {
std::cout << "Generalized Outer Product f(x,y)=x+2y:" << std::endl;
int ** mat = nullptr;
mat = Generalized_Outer_Product(a.begin(), a.end(), b.begin(), b.end(), [](int x, int y) {
return x + 2 * y;
});
for (int i = 0; i < n1; i++) {
for (int j = 0; j < n2; j++)
std::cout << mat[i][j] << " ";
std::cout << std::endl;
}
for (int i = 0; i < n1; i++)
delete[] mat[i];
delete[] mat;
} catch (std::range_error e) {
std::cout << e.what();
}
return 0;
}
Could you help me to accept lambda function on the correct way?
TL DR
Just leave the type of the parameter open and let the compiler deal with invalid parameter types; use decltype
to determine the result element type:
template<class Iterator1, class Iterator2, class Function>
auto GeneralizedOuterProduct(Iterator1 const start1, Iterator1 const end1, Iterator2 const start2, Iterator2 const end2, Function&& f)
{
using ResultType = decltype(f(*start1, *start2));
...
}
Additional Advice + Full Code
First create yourself a type that manages the lifetime of the data. This simplifies the exception handling. If done correctly you can simply let your exceptions go unhandled in the GeneralizedProduct
function without having to worry about memory leaks.
The following class simply uses a single std::vector
to store the data of one row after the other. The index operator uses the fact that the vector iterator allows us to write iter[index]
to get a reference to the element index
elements after the iterator position.
template<class T>
struct MultiplicationResult
{
public:
using ValueType = T;
using IndexOperatorElement = std::vector<ValueType>::iterator;
using IndexOperatorElementConst = std::vector<ValueType>::const_iterator;
MultiplicationResult(size_t dimension1, std::vector<T>&& data)
: m_dimension1(dimension1)
{
if (data.size() % dimension1 != 0)
{
throw std::runtime_error("invalid data: the number of data elements is not divisible by dimension1");
}
m_dimension2 = data.size() / dimension1;
m_data = std::move(data);
}
IndexOperatorElement operator[](size_t index)
{
return m_data.begin() + index * m_dimension2;
}
IndexOperatorElementConst operator[](size_t index) const noexcept
{
return m_data.cbegin() + index * m_dimension2;
}
size_t Dimension1() const noexcept
{
return m_dimension1;
}
size_t Dimension2() const noexcept
{
return m_dimension2;
}
private:
std::vector<T> m_data;
size_t m_dimension1;
size_t m_dimension2;
};
Now all for the GeneralizedProduct
function to do is to determine the result type and create the vector that will be used as data for MultiplicationResult
.
Note that we do not restrict the type of the function passed here in any way. The compilation will result in errors, if the call operator of the argument passed in cannot be called given the the dereferenced iterators or if the result type isn't something that can be used as template parameter of std::vector
. Note that lambdas are objects with a call operator and therefore not assignable to result_type(argument_type1, argument_type2)
. (I consider this approach more convenient than using std::function<result_type(argument_type1, argument_type2)>
:
template<class Iterator1, class Iterator2, class Function>
auto GeneralizedOuterProduct(Iterator1 const start1, Iterator1 const end1, Iterator2 const start2, Iterator2 const end2, Function&& f)
we determine the result type calling the call operator simply using decltype
:
using ResultType = decltype(f(*start1, *start2));
template<class Iterator1, class Iterator2, class Function>
auto GeneralizedOuterProduct(Iterator1 const start1, Iterator1 const end1, Iterator2 const start2, Iterator2 const end2, Function&& f)
{
using ResultType = decltype(f(*start1, *start2));
size_t const n1 = std::distance(start1, end1);
// if an exception happens, data will ensure the elements already allocated get destroyed
std::vector<ResultType> data;
data.reserve(n1 * std::distance(start2, end2));
for (Iterator1 pos1 = start1; pos1 != end1; ++pos1)
{
for (Iterator2 pos2 = start2; pos2 != end2; ++pos2)
{
data.push_back(f(*pos1, *pos2));
}
}
return MultiplicationResult(n1, std::move(data)); // C++17 CTAD deduces the template arguments here
}
Example usage (without exception handling:
int main() {
std::vector<int> a{ 5, 2, 8 };
std::vector<int> b{ 1, 3, 6, 2 };
auto result = GeneralizedOuterProduct(a.begin(), a.end(), b.begin(), b.end(), [](int x, int y) { return x + 2* y; });
size_t const d1 = result.Dimension1();
size_t const d2 = result.Dimension2();
for (size_t i = 0; i != d1; ++i)
{
auto innerArray = result[i];
for (size_t j = 0; j != d2; ++j)
{
std::cout << std::setw(5) << innerArray[j];
}
std::cout << '\n';
}
}