Search code examples
c++genericsstdvector

Sum of two containers in generic function


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?


Solution

  • 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';
    }