1

I am just writing on a MathVector class

template<typename T> MathVector
{
   using value_type = T;

   // further implementation
};

However, the class is thought to work with fundamental types but also with a, lets say, Complex class

template<typename T> Complex
{
   using value_type = T;

   // further implementation
};

which offers for example the member functions

template<typename T> Complex<T>& Complex<T>::operator*=(const Complex<T>& c);
template<typename T> Complex<T>& Complex<T>::operator*=(const T& c);

Now, for the MathVector class also a multiplication is definded:

template<typename T> MathVector<T>& MathVector<T>::operator*=(const MathVector<T>& c);

This is fine for T=double, but for T=Complex<double> I would like to have the possibility to multiply also with double without first converting it to Complex<double> (much more efficient).

This is aggravated by the fact that the Code should also work in CUDA device code (I ommited the specifier __host__ __device__ for brevity). This means that the standard library tools will not be helpful.

First I thought of an additional template parameter

template<typename T, typename U> MathVector<T>& MathVector<T>::operator*=(const U& c);

But this seems dangerous to me, because U can be a lot of more than T or T::value_type. (In fact I had this parameter also in the Complex class first - the compiler was not able any more to decide wich template to use, the one of the Complex class or the one of the MathVector class.)

The second idea is to use template specialization

template<typename T, typename U> MathVector<T>& MathVector<T>::operator*=(const U& c)
{
   static_assert(sizeof(T) == 0, "Error...");
}
template<typename T> MathVector<T>& MathVector<T>::operator*=(const typename T::value_type& c)
{
   // implementation
}

But this will not work with fundamental types any more!

I have seen the solutions of this (or a very similar) problem in C++ Operator Overloading for a Matrix Class with Both Real and Complex Matrices and Return double or complex from template function, but they are solved using the standard library in a way which is not possible for CUDA.

So my question is: Is there a way to overload the operator that works with fundamental types and with types that serve a value_type but not for others - without using std:: stuff that the nvcc compiler will reject?

Community
  • 1
  • 1
marlam
  • 590
  • 5
  • 14
  • I'm not sure what's the problem here, you could declare all the overloads for it, like `template MathVector& MathVector::operator*=(const MathVector& c);`, `template MathVector& MathVector::operator*=(const T& c);` and `template MathVector& MathVector::operator*=(const typename T::value_type& c);`. – songyuanyao Aug 26 '16 at 14:00
  • You want the 4 overloads `MathVector>::operator*=(const U&);` with `U` in `Complex`, `T`, `MathVector`, `MathVector>` ? – Jarod42 Aug 26 '16 at 14:02
  • @songyuano: The problem is, that the overload `template MathVector& MathVector::operator*=(const typename T::value_type& c);` will not compile for fundamental types. (Btw the multiplication for with a second `Mathvector` makes no sense, because this would be a scalar product and would return a `T`, which cannot make sense in the `operator*=` - I wrote a `operator*` for that case, but this is not important for the question.) – marlam Aug 26 '16 at 14:28
  • @Jarod42 : See my first comment. I would like to have `MathVector::operator*=(const fundamental_type& c>`, `MathVector>::operator*=(const Complex& c)` and `MathVector>::operator*=(const T& c)` (but more general, as I tried to explain in the question) – marlam Aug 26 '16 at 14:32
  • How does CUDA support SFINAE/`decltype` ?, as you want `MathVectoroperator *= (const U&)` for which `T *= U` is valid – Jarod42 Aug 26 '16 at 14:47
  • I didn't know the power of SFINAE until now - but wikipedia has a very nice article [SFINAE](https://de.wikipedia.org/wiki/Substitution_failure_is_not_an_error). I think this technique can solve the problem, also for CUDA. If you write an answer I would love to upvote it ;-). – marlam Aug 26 '16 at 15:39

2 Answers2

2

You could make operator*= non-member function templates, and provide all the overloads, make SFINAE to take effect.

template<typename T>
MathVector<T>& operator*=(MathVector<T>& m, const MathVector<T>& c);
template<typename T>
MathVector<T>& operator*=(MathVector<T>& m, const T& c);
template<typename T>
MathVector<T>& operator*=(MathVector<T>& m, const typename T::value_type& c);

Then call them as:

MathVector<Complex<double>> m1;
m1 *= MathVector<Complex<double>>{};  // call the 1st one
m1 *= Complex<double>{};              // call the 2nd one
m1 *= 0.0;                            // call the 3rd one

MathVector<double> m2;
m2 *= MathVector<double>{};           // call the 1st one
m2 *= 0.0;                            // call the 2nd one

LIVE

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • Thx for the answer. This is the implementation I have chosen now. Perhaps two annotations: (1) Still the first overload makes no sense here, although it is useful for demonstration of the programming technique. (2) The crucial point in comparison to my second attempt (see question) seems to be that the operator is now a _non-member_ function. But I don't know, why this is so important here. Perhaps you could add a bit information about that. – marlam Aug 26 '16 at 16:57
  • @marlam: SFINAE applies to template function. In the template class, the function was not template. – Jarod42 Aug 26 '16 at 17:28
  • @marlam It doesn't work with member functions, because when instantiate the class template (like `MathVector m;`) the member functions (at least their declaration) will be instantiated too, then compile would fail since the parameter type `T::value_type` for `T=double` is invalid. Non-member functions doesn't have such trouble, and as Jarod42 said, we have to make them template to be applicable for SFINAE. – songyuanyao Aug 27 '16 at 10:12
2

With SFINAE, and decltype, you may do something like (c++11):

template<typename T, typename U>
auto
MathVector<T>::operator*=(const U& c)
-> decltype(void(std::declval<T&>() *= c), std::declval<MathVector<T>&>())
{
    // Your implementation
}
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Thx for the answer. Unfortunately it uses `std::declval` and is therefore not directly applicable to the problem (CUDA). Therefore I prefer the other answer. – marlam Aug 26 '16 at 16:51
  • `template T declval();` might be a replacement in your case. – Jarod42 Aug 26 '16 at 17:29