Nested vectors do not follow promotion rules. Solution has a bug.

63 views Asked by At

This program adds nested vectors, but does not promote the types correctly. I think std::plus needs to take in T1 or T2 depending on basic type promotion rules. The original problem is in this post (decltype does not resolve nested vectors. How can I use templates for nested vectors?).

main.cpp

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

template<typename T1>
std::ostream& operator<<(std::ostream& stream, std::vector<T1> r){
    if(r.size() == 0){
        return stream;
    }
    else{
        stream << "(";
        for(int i = 0; i < r.size(); i++){
            if(i < (r.size() - 1)){
                stream << r[i] << ", ";
            }
            else{
                stream << r[i] << ")";
            }
        }
    }
    return stream;
};

template <typename T1, typename T2>
struct Add : std::plus<T1> { };//<-Here T1 or T2 depending on their types.

template <typename T1, typename T2>
struct Add<std::vector<T1>, std::vector<T2>>
{   
    auto operator()(const std::vector<T1>& l, const std::vector<T2>& r)
        -> std::vector<decltype(Add<T1,T2>{}(l[0], r[0]))>
    {
        using type = decltype(Add<T1,T2>{}(l[0], r[0]));
        std::vector<type> ans;

        if(l.size() == std::max(l.size(),r.size()))
            std::transform(r.begin(), r.end(), l.begin(), std::back_inserter(ans), Add<T1,T2>{});
        else
            std::transform(l.begin(), l.end(), r.begin(), std::back_inserter(ans), Add<T1,T2>{});
        return ans;
    };
};

template <typename T1, typename T2>
auto operator+(const std::vector<T1>& lhs, const std::vector<T2>& rhs)
    -> decltype(Add<std::vector<T1>, std::vector<T2>>{}(lhs, rhs))
{
    return Add<std::vector<T1>, std::vector<T2>>{}(lhs, rhs);
}

int main(){
    std::vector<int> e = {1};
    std::vector<double> q = {1.5};

    //Incorrect result = 2
    std::cout << (e + q) << std::endl;
    //Correct result = 2.5
    std::cout << (q + e) << std::endl;

    return 0;
}
2

There are 2 answers

0
Barry On BEST ANSWER

There are two issues in your code (that I got wrong in my answer to your original question). The first, as mentioned by vsoftco is using std::common_type for the base case.

The second is a simple error. Consider this if statement:

    if(l.size() == std::max(l.size(),r.size()))
        std::transform(r.begin(), r.end(), l.begin(), std::back_inserter(ans), 
                       Add<T1,T2>{});
    else
        std::transform(l.begin(), l.end(), r.begin(), std::back_inserter(ans), 
                       Add<T1,T2>{});

Add<T1,T2>::operator(), in the partial specialization case, takes two arguments: a std::vector<T1> and a std::vector<T2>, in that order. But in the two branches of the if statement, we're actually calling them in different orders. In the true branch, we're calling them in the REVERSE order, so just have to invert the ordering in the template:

    if(l.size() == std::max(l.size(),r.size()))
        std::transform(r.begin(), r.end(), l.begin(), std::back_inserter(ans), 
                       Add<T2,T1>{});

Should actually just remove the if-statement altogether so that the specialization case is now:

template <typename T1, typename T2> 
struct Add<std::vector<T1>, std::vector<T2>>
{   
    using R = decltype(Add<T1,T2>{}(std::declval<T1>(), std::declval<T2>()));

    std::vector<R> operator()(const std::vector<T1>& l, const std::vector<T2>& r)
    {
        std::vector<R> ans;

        std::transform(l.begin(),
                       l.begin() + std::min(l.size(), r.size()),
                       r.begin(),
                       std::back_inserter(ans),
                       Add<T1,T2>{});
        return ans;
    };
};
5
vsoftco On

Use std::common_type when defining the primary template Add<>, like this (must #include <type_traits>):

template <typename T1, typename T2>
struct Add : std::plus<typename std::common_type<T1,T2>::type> { };//<-Here T1 or T2 depending on their types.

You basically tell the Add primary template to use the "best" type. Then the specialization will use the primary, and the latter now takes care of the promotion.

Live on Coliru