Search code examples
c++templatesoperator-overloadingoverloadingoverload-resolution

overload resolution fails for derived class


I am using a class mArray, which implements numerical data containers with variable dimensionality

template <typename T>
   class mArray<T>: {
     ...
     std::vector<T> my_data;
   }

Another class mImage inherits from mArray and provides image-specific operations, and another class mDcmImage inherits from mImage and provides format-specific functions.

I thought this was a very clean way to separate different types of functionality.

One example or mArray functionality: element-wise addition:

// add a mArray to the data [1]
template <typename U>
void operator+= (const mArray<U>& other) {
    if (other.getSizes() != getSizes())
        throw std::invalid_argument {"incompatible sizes"};
    else {
        std::transform (my_data.begin(),
                        my_data.end(),
                        my_data.begin(),
                        my_data.begin(),
                        []( const T &a, const U &b) { return a+b; } );
    }
    return;
}

// add an element to the data [2]
template <typename U>
void operator+= (const U& rhs) {
    assert (!std::is_arithmetic<U>::value);
    std::transform (my_data.begin(),
                    my_data.end(),
                    my_data.begin(),
                    [&rhs]( const T &lhs) { return lhs+rhs; } );
    return;
}

(getSizes() is an mArray function)
But now my code loads a mDcmImage<int> from a file, and when I use

typedef int intensity;
mDcmImage<intensity> im1 ("/tmp/test1.im");
mDcmImage<intensity> im2 ("/tmp/test2.im");
im1 += im2;

then I get the following error:

mArray.hpp required from ‘struct mArray<T>::operator+=(const U&) 
           [with U = mDcmImage<int>; T = int]::<lambda(const int&)>’|
mArray.hpp required from ‘void mArray<T>::operator+=(const U&) 
           [with U = mDcmImage<int>; T = int]’|
  test.cpp required from here|
mArray.hpp error: no match for ‘operator+’ in ‘lhs + rhs’|

In other words: although I coded the addition of another mArray as well as the addition of one value, when I call the += operator in the main program to add two arrays together, it uses the += implementation for a single value.

I have tried several things, such as

  • using std::enable_if<std::is_arithmetic<U>::value >::type* for the value version of operator+= -- not allowed because operator+= takes strictly 1 argument
  • defining both versions of operator+= for mImage and mDcmImage as well -- at those levels it also uses the wrong implementation.

In a simplified example the right version of the operator is selected -- why not now? I cannot see why the overload resolution fails here.


Solution

  • After template argument deduction, the second version becomes a perfect match:

    template <typename U> void operator+= (const U& rhs); // U = mDcmImage<int>
    

    compared to the first version, which requires a conversion from mDcmImage to mArray:

    template <typename U> void operator+= (const mArray<U>& other); // U = int
    

    so it is selected by overload resolution.


    The simplest fix is probably to modify the single-value version to just take a T and rely on implicit conversions:

    void operator+= (const T& rhs);
    

    SFINAE is also possible, for instance in the return type:

    template <typename U> 
    typename std::enable_if<std::is_arithmetic<U>::value>::type operator+= (const U& rhs);