-1

I need to write a generic function that finds sum of elements that belong to two containers and put those elements inside a vector which type is the result of sum.

EXAMPLE:

container one: 5 2 8 3 6

container two: 6 1 5

container of sum: 11 3 13 3 6

#include <iostream>
#include <cmath>
#include <vector>

template < typename tip1, typename tip2, typename tip >
  tip sum_of_containers(tip1 blok1, tip2 blok2) {
    std::vector < tip > a;
    int n1 = std::distance(blok1.begin(), blok1.end());
    int n2 = std::distance(blok2.begin(), blok2.end());
    int n;
    n = n1;
    if (n2 > n1) n = n2;
    for (int i = 0; i < n; i++)
      a[i].push_back(blok1[i] + blok2[i]);
    return a;
  }

int main() {
  int n1, n2, x;
  std::cin >> n1;
  std::vector < double > 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);
  }
  c = sum_of_containers(a, b);
  for (double i: c)
    std::cout << i << " ";
  return 0;
}

errors on line 31:

no matching function for call to 'sum_of_containers

couldn't deduce template parameter 'tip'

Could you give me some approach or idea how to solve this problem?

Rocket Procd
  • 103
  • 10
  • Does the error make sense? What should `typename tip` be when you call `sum_of_containers(a,b)`? Your function claims to need three distinct types `tip1`, `tip2`, and `tip`, but maybe one distinct type is enough? – Drew Dormann Apr 22 '22 at 15:43
  • You also have undefined Behavior in `blok1[i] + blok2[i]`, as your code seems to recognize that `i` might not be a valid index for both those containers. If `block2` has 3 elements, what does `blok2[4]` mean? – Drew Dormann Apr 22 '22 at 15:49
  • The problem is `tip` cannot be deduced. You need to specify it explicitly when calling `sum_of_containers`. But it is the last argument, so you also need to provide the other two arguments first, forcing you to specify all arguments. Move `tip` to the front of the argument list and call `sum_of_containers>(a, b);` or find a way to deduce `tip` based on `tip1` or `tip2`. – François Andrieux Apr 22 '22 at 15:54
  • `if (n2 > n1) n = n2;` is making sure `n` is the largest of `n1` or `n2`. You probably want to opposite. – François Andrieux Apr 22 '22 at 15:55
  • You are also making copies of entire vectors when calling your function. [Consider making the parameters const references](https://stackoverflow.com/questions/2582797/why-pass-by-const-reference-instead-of-by-value). – Drew Dormann Apr 22 '22 at 17:08
  • @DrewDormann in the main function, both vector a and vector b have the same type, however this generic function will be tested on two blocks which have different types. – Rocket Procd Apr 22 '22 at 23:10
  • @FrançoisAndrieux if I remove type `tip` and use `tip2` or `tip1` for vector a in generic function that still wouldn't solve the problems – Rocket Procd Apr 22 '22 at 23:12
  • 1
    @RocketProcd It does solve the problem, but that reveals more subsequent problems. Your code has multiple problems, and fixing one is revealing the next. The problem should no longer be "couldn't deduce template parameter", it will be something else. – François Andrieux Apr 23 '22 at 00:09

1 Answers1

1

The question is very unclear.

"Generic" could mean different containers, like std::vector or std::deque. But in your function function you are using a std::vector and the index operator[] and the function push_back. The only 2 containers having all this are std::vector and std::deque. So, this would not make that much sense.

The next level of "generic" would be to have different data types for a std::vector. But with 3 template parameters, this would mean worst case that we add 2 different data types and assign them to a 3rd, again different data type.

Logically this would create a lot of other troubles, type casting would be needed and loss of precision could be the result.

If we look in the function main, then we see, that 3 std::vectors, all with the same data type double are instantiated. This makes sense. And this would restrict the "generic" function to have one common templatized parameter for a type that a std::vector would hold.

This could then look like the following:

#include <iostream>
#include <vector>
#include <algorithm>

template <typename T>
std::vector<T> sumOfVectors(const std::vector<T>& t1, const std::vector<T>& t2) {

    // Get a reference to the larger vector
    const std::vector<T>& largerVector = (t1.size() > t2.size()) ? t1 : t2;

    // Create the resulting vector that can hold all elements
    std::vector<T> result(largerVector.size(), {});

    size_t index{};
    for (; index < std::min(t1.size(),t2.size()); ++index)
        result[index] = t1[index] + t2[index];

    for (; index < largerVector.size(); ++index)
        result[index] = largerVector[index];

    return result;
}
int main() {
    int n1, n2, x;
    std::cin >> n1;
    std::vector < double > 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);
    }
    c = sumOfVectors(a, b);
    for (double i : c)
        std::cout << i << " ";
    return 0;
}

But of course you would define an operator + for this, which gives us a more intuitive result:

#include <iostream>
#include <vector>
#include <algorithm>

template <typename T>
std::vector<T> operator +(const std::vector<T>& t1, const std::vector<T>& t2) {

    // Get a reference to the larger vector
    const std::vector<T>& largerVector = (t1.size() > t2.size()) ? t1 : t2;

    // Create the resulting vector that can hold all elements
    std::vector<T> result(largerVector.size(), {});

    size_t index{};
    for (; index < std::min(t1.size(),t2.size()); ++index)
        result[index] = t1[index] + t2[index];

    for (; index < largerVector.size(); ++index)
        result[index] = largerVector[index];

    return result;
}
int main() {
    int n1, n2, x;
    std::cin >> n1;
    std::vector < double > 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);
    }
    c = a + b;
    for (double i : c)
        std::cout << i << " ";
    return 0;
}

Edit

With the exact task description given in the comment, we can come up with the needed solution.

It is a little bit more heavy.

We could even add type traits to check, if the containers are iterable, but maybe that is too much (though easy in C++20 with something like if constexpr (std::ranges::range<Container>)

Anyway, please see the updated solution with 2 test cases.

#include <iostream>
#include <type_traits>
#include <vector>
#include <deque>
#include <forward_list>
#include <array>
#include <list>
#include <string>

// Some aliases to avoid heavy typing
template <typename T, typename U>
using Value_t = typename std::common_type<typename T::value_type, typename U::value_type>::type;
template <typename T, typename U>
using Vector_t = typename std::vector<Value_t<T, U>>;

template <typename T, typename U>
auto sum_of_containers(const T& c1, const U& c2) -> Vector_t<T, U> {

    // Get rid of template parameters using aliases
    using MyType = Value_t<T,U>;
    using MyVector = Vector_t<T, U>;

    // Here we will store the result
    MyVector result{};

    typename T::const_iterator c1Iter = std::begin(c1);
    typename U::const_iterator c2Iter = std::begin(c2);
   
    // Add, as long as there are the same number of elements in the containers
    while ((c1Iter != std::end(c1)) and (c2Iter != std::end(c2))) 
        result.push_back(static_cast<MyType>(*c1Iter++) + static_cast<MyType>(*c2Iter++));
        
    // If there should still be elements in one of the containers, then add them to the resulting vector as is
    while (c1Iter != std::end(c1))
        result.push_back(static_cast<MyType>(*c1Iter++));
    while (c2Iter != std::end(c2))
        result.push_back(static_cast<MyType>(*c2Iter++));
        
    return result;
}

int main() {

    // Test Data 0
    std::deque<float> fl1 { 0.1f, 0.2f, 0.3f };
    std::deque<double> dbl1 { 0.1, 0.2, 0.3, 0.4, 0.5 };

    auto result0 = sum_of_containers(fl1, dbl1);
    for (const auto& x0 : result0)
        std::cout << x0 << '\n';
    std::cout << '\n';


    // Test Data 1
    std::deque<int> dq{ -1,2,-3 };
    std::forward_list<float> fl{ 0.1f, 0.2f, 0.3f, 0.4f, 0.5f };

    auto result1 = sum_of_containers(dq, fl);
    for (const auto& x1 : result1)
        std::cout << x1 << '\n';
    std::cout << '\n';

    // Test Data 2
    std::array<unsigned long, 3> ar1{ 1ul,2ul,3ul };
    std::list<double> dbl{ 0.1, 0.2, 0.3, 0.4, 0.5 };

    auto result2 = sum_of_containers(ar1, dbl);
    for (const auto& x2 : result2)
        std::cout << x2 << '\n';
    std::cout << '\n';
}


.

.

.


And of course, but not needed, you would implement also the + operator here.

#include <iostream>
#include <type_traits>
#include <vector>
#include <deque>
#include <forward_list>
#include <array>
#include <list>
#include <string>

// Some aliases to avoid heavy typing
template <typename T, typename U>
using Value_t = typename std::common_type<typename T::value_type, typename U::value_type>::type;
template <typename T, typename U>
using Vector_t = typename std::vector<Value_t<T, U>>;

template <typename T, typename U>
auto operator +(const T& c1, const U& c2) -> Vector_t<T, U> {

    // Get rid of template parameters using aliases
    using MyType = Value_t<T, U>;
    using MyVector = Vector_t<T, U>;

    // Here we will store the result
    MyVector result{};

    typename T::const_iterator c1Iter = std::begin(c1);
    typename U::const_iterator c2Iter = std::begin(c2);

    // Add, as long as there are the same number of elements in the containers
    while ((c1Iter != std::end(c1)) and (c2Iter != std::end(c2)))
        result.push_back(static_cast<MyType>(*c1Iter++) + static_cast<MyType>(*c2Iter++));

    // If there should still be elements in one of the containers, then add them to the resulting vector as is
    while (c1Iter != std::end(c1))
        result.push_back(static_cast<MyType>(*c1Iter++));
    while (c2Iter != std::end(c2))
        result.push_back(static_cast<MyType>(*c2Iter++));

    return result;
}

int main() {

    // Test Data 0
    std::deque<float> fl1{ 0.1f, 0.2f, 0.3f };
    std::deque<double> dbl1{ 0.1, 0.2, 0.3, 0.4, 0.5 };

    auto result0 = fl1 + dbl1;
    for (const auto& x0 : result0)
        std::cout << x0 << '\n';
    std::cout << '\n';


    // Test Data 1
    std::vector<int> ve{ -1,2,-3 };
    std::forward_list<float> fl{ 0.1f, 0.2f, 0.3f, 0.4f, 0.5f };

    auto result1 = ve + fl;
    for (const auto& x1 : result1)
        std::cout << x1 << '\n';
    std::cout << '\n';

    // Test Data 2
    std::array<unsigned long, 3> ar1{ 1ul,2ul,3ul };
    std::list<double> dbl2{ 0.1, 0.2, 0.3, 0.4, 0.5 };

    auto result2 = ar1 + dbl2;
    for (const auto& x2 : result2)
        std::cout << x2 << '\n';
    std::cout << '\n';
}
A M
  • 14,694
  • 5
  • 19
  • 44
  • This code failed on my university online IDE for autotest which tests using `std::deque` and `std::deque` – Rocket Procd Apr 23 '22 at 12:14
  • here's the complete task setting: https://ibb.co/Bs0XYcH could you modify your code to work for this task setting? I'm sorry that my explanation was not so clear, but if I put the whole explanation in question, I would get many downvotes and question would be closed – Rocket Procd Apr 23 '22 at 12:20
  • Thank you for adding more details for clarification. Please see my Edit. Please comment, if you need more. – A M Apr 23 '22 at 17:31
  • rather than `common_type`, why not use `+`? `decltype(std::declval>() + std::declval>())` – Caleth Apr 25 '22 at 09:21
  • @Caleth: IMHO the template name `common_type` is more intuitive in this context. But of course tons of different soultions are possible . . . – A M Apr 25 '22 at 09:28