5
votes

My goal is to be able to use arithmetic operators on std::vector. Consider the following example:

#include <vector>

using namespace std;

template <class S, class T, class U> vector<U> operator+(const vector<S> &a, const vector<T> &b){
    vector<U> result;
    result.reserve(a.size());
    for(int i = 0; i < a.size();++i){
        result[i] = a[i] + b[i];
    }
    return result;
}

int main(int argc, char** argv) {
    vector<double> bla;
    bla = bla + bla;
    return 0;
}

This code does not compile because the compiler is not able to deduce template argument U (it's not an MWE but I tried to provide an example that makes sense). Why is this the case? I know that it might not make sense to use three template arguments here. My idea was that in the case where types S and T both provide a matching '+'-implementation with different return types, I could handle both cases at once. Or would then be a problem with ambiguity? I'm just wondering if the compiler shouldn't be able to deduce U. Of course the following code just works fine:

#include <vector>

using namespace std;

template <class S, class T> vector<S> operator+(const vector<S> &a, const vector<T> &b){
    vector<S> result;
    result.reserve(a.size());
    for(int i = 0; i < a.size();++i){
        result[i] = a[i] + b[i];
    }
    return result;
}

int main(int argc, char** argv) {
    vector<double> bla;
    bla = bla + bla;
    return 0;
}
4
You may be interested in std::common_type: en.cppreference.com/w/cpp/types/common_type - aschepler
This is a dupe, but I can't find it. Basically, what you do with the result has no effect on overload resolution whatsoever. Ergo, it's trying to compile bla+bla, but there's not enough information there to deduce the template parameter U. - Mooing Duck
The C++ way of doing this is using std::transform - Neil Kirk
std::valarray already implements this. - chris

4 Answers

1
votes

You can accomplish this with the use of common type

#include <vector>
#include <type_traits>

using namespace std;

template <class S, class T> 
vector<typename std::common_type<S, T>::type> operator+(const vector<S> &a, const vector<T> &b)
{
    vector<typename std::common_type<S, T>::type> result;
    result.reserve(a.size());
    for(unsigned int i = 0; i < a.size();++i){
        result[i] = a[i] + b[i];
    }
    return result;
}

int main() {
    vector<double> bla;
    bla = bla + bla;
    return 0;
}

Live Example

Edit: as Jarod42 suggested, you might also use vector<decltype(a[0] + b[0])> as another possible return type (which may be different than the common_type). Keep in mind that this latter one requires trailing return type or std::declval (C++11)

1
votes

Marco A.'s solution is good,I want to provide more details ,and show why Macro A's answer is better.

solution 1: As you make the return type of operator+ as the 3rd parameter ,so when explicity declare template parameter ,we have to specify all three parameter,like this:

#include <iostream>     // std::cout
#include <iterator>     // std::ostream_iterator
#include <vector>       // std::vector
#include <algorithm>    // std::copy

// declare return type as the 3rd parameter
template <class S, class T, class U> std::vector<U> operator+(const std::vector<S> &a, const std::vector<T> &b){
    std::vector<U> result(a.size());
    for(int i = 0; i < a.size();++i){
        result[i] = a[i] + b[i];
    }
    return result;
}

int main(int argc, char** argv) {
    std::vector<double> bla;
    bla.push_back(3.14);
    bla.push_back(2.7);

    // explicity specify three  template parameter
    bla = ::operator+<double,double,double>(bla,bla); 

    // print results   
    std::ostream_iterator<double> os_it(std::cout," ");
    std::copy(bla.begin(),bla.end(),os_it);
    std::cout << std::endl;

    return 0;
}

output is :

6.28 5.4

solution 2: you can decalre the return type as the first parameter,then we only have to specify the return type ,and the other two parameter can be deduce by compiler .

#include <iostream>     // std::cout
#include <iterator>     // std::ostream_iterator
#include <vector>       // std::vector
#include <algorithm>    // std::copy

//decalre return type as first template parameter
template <class U, class S, class T> std::vector<U> operator+(const std::vector<S> &a, const std::vector<T> &b){
    std::vector<U> result(a.size());
    for(int i = 0; i < a.size();++i){
        result[i] = a[i] + b[i];
    }
    return result;
}

int main(int argc, char** argv) {
    std::vector<double> bla;
    bla.push_back(3.14);
    bla.push_back(2.7);

    // only explicity specify the return type 
    bla = ::operator+<double>(bla,bla);

    // print results   
    std::ostream_iterator<double> os_it(std::cout," ");
    std::copy(bla.begin(),bla.end(),os_it);
    std::cout << std::endl;

    return 0;
}

output is :

6.28 5.4 

solution 3 : As Macro A. gave ,that is better than the above two solution. we needn't have to explicity specify the return type.As a complete example ,I also put my runnable example code here :

//Note:  This file should be compiled with c++11

#include <iostream>     // std::cout
#include <iterator>     // std::ostream_iterator
#include <vector>       // std::vector
#include <algorithm>    // std::copy
#include <type_traits>  // std::common_type c++11

//using std::common_type 
template <class S, class T> std::vector<typename std::common_type<S, T>::type> operator+(const std::vector<S> &a, const std::vector<T> &b){
    std::vector<typename std::common_type<S, T>::type> result(a.size());
    for(int i = 0; i < a.size();++i){
        result[i] = a[i] + b[i];
    }
    return result;
}

// print results   
template <typename T>
void printData(const std::vector<T>& bla) {
    std::ostream_iterator<double> os_it(std::cout," ");
    std::copy(bla.begin(),bla.end(),os_it);
    std::cout << std::endl;
}


int main(int argc, char** argv) {
    std::vector<double> dvec;
    dvec.push_back(3.14);
    dvec.push_back(2.7);

    std::vector<int> ivec;
    ivec.push_back(1);
    ivec.push_back(2);

    // we need not to  explicity specify template parameter 
    printData(dvec+dvec);
    printData(dvec+ivec);
    printData(ivec+ivec); 

    return 0;
}

output is :

6.28 5.4 
4.14 4.7 
2 4 
0
votes

How would the compiler deduce U? There is no information provided that says anything at all about U.

Change it to just have S and T, and make the return type depend on some relationship between S and T.

For example.

template<typename S, typename T>
auto operator+(vector<S> const &a, vector<T> const &b) -> vector< foo<S,T> >
0
votes

The standard already provides an arithmetic-based array class std::valarray:

std::valarray<double> bla{2.0, 4.0, 6.0};
bla = bla + bla;