meowsqueak meowsqueak - 3 months ago 12
C++ Question

C++ - How to specialise a custom type for std::max without overloading comparison operators?

I'm looking for advice on how to implement a specialised "max" (i.e. maximum) function for a custom data type that also works with scalar types like

float
. The data type I'm writing is a wrapper for a vector (eventually a SIMD vector of four floats, not a
std::vector
) and I want to provide a
max
function to compare two vectors and return a new vector that is the maximum of each element. This is different to
std::max
which uses the comparison operator but the concept is the same.

The problem is that I have a generic function called
do_max(T x, T y)
that applies
max
to the inputs. I need this function to work for both scalar float inputs (e.g.
do_max<float>(0.1f, 0.2f)
) and my vector class (e.g.
do_max<MyVector>(v0, v1)
).

Note that overloading MyVector's comparison operators is not an option because I'm using those with the SIMD intrinsics that are quite different: they create an integer vector containing 1, 0, -1 for each element comparison rather than returning a boolean result.

The code I have below does not compile unless you comment out the
float f0 = ...
line:

// compile with: g++ -std=c++11 max.cc -o max
#include <algorithm>
#include <vector>

class MyVector {
public:
MyVector(float x0, float x1, float x2, float x3) : storage_ { x0, x1, x2, x3 } {};

friend MyVector max(MyVector lhs, const MyVector & rhs);

private:
std::vector<float> storage_;
};

MyVector max(MyVector lhs, const MyVector & rhs) {
for (size_t i = 0; i < lhs.storage_.size(); ++i) {
lhs.storage_[i] = std::max(lhs.storage_[i], rhs.storage_[i]);
}
return lhs;
}

template<typename T>
T do_max(const T & x, const T & y) {
// if this is std::max then it won't compile for MyVector
return max(x, y);
}

int main(int argc, char * argv[]) {

MyVector v0 { 0.1, 0.2, 0.3, 0.4 };
MyVector v1 { 0.4, 0.3, 0.2, 0.1 };

MyVector v2 = do_max(v0, v1);

// Comment out the following line to successfully compile.
// However I'd like this to work for scalar types too:
float f0 = do_max(0.1f, 0.2f);

return 0;
}


I have a feeling I need a way to make that
max
function resolve to
std::max
for scalar types, and my specialised
max
friend function for the MyVector type.

How to I define a max function that works in this way? Is it better to forget about
std::max
and use my own max function that is specialised for
MyVector
and also provides implementation for scalar types like
float
?

The background for this is that I'm implementing a data path that I hope will work with both MyVector and scalar types (as a parameterised type at compile time). I already have arithmetic working, however the solution for
max
will be used with other functions like
min
,
exp
,
pow
as well.

Answer

Classic solution:

template<typename T>
T do_max(const T & x, const T & y) {
  using std::max;
  return max(x, y);
}

Argument-Dependent Lookup finds your max, for float this does not happen and you get std::max<float>.