Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++: Custom return value for different types of template function (method)

Motivation - in abstract mathematics we can generalise 3-dimensional cross product to n dimensions, assuming that for two vectors v,w of dimension n, their cross product will have dimension n(n-1)/2. Skipping the ideas of differential forms, I would like to converse this idea into the language of templates in C++ library. I had this idea:

auto Cross(const Vec<dim, float_t> &rhs) {
    if (dim == 0)return;
    if (dim == 1)return (float_t)0;
    if (dim == 2)return Values[0] * rhs.Values[1] - Values[1] * rhs.Values[0];
    if (dim == 3)return Vec<dim, float_t>(
        Values[1] * rhs.Values[2] - Values[2] * rhs.Values[1], 
        Values[2] * rhs.Values[0] - Values[0] * rhs.Values[2], 
        Values[0] * rhs.Values[1] - Values[1] * rhs.Values[0]);

    //...
}

but sadly, auto specifier doesn't allow for different return types. I could of course mess with preprocessor, but I believe that there is more clever way to do it, something like:

Vec<dim*(dim-1)/2,float_t> Cross(const Vec<dim,float_t> &rhs);

(with void as a special case for zero-dimensional point and just float_t for one-dimensional point). How to solve this in a smart way?

like image 541
Ch3shire Avatar asked May 28 '26 19:05

Ch3shire


2 Answers

I believe you could do this:

template <std::size_t dim>
auto cross(const Vec<dim, float_t>& rhs)
{
    /*logic goes here, also you can generalize*/    
}

This will create new function for each dim (implicitly). Also, try to do it at compile time rather than runtime by using specializations.

like image 124
Incomputable Avatar answered May 30 '26 10:05

Incomputable


One way to implent this is by means of specializations. Without specialization it would be very hard to implement a truly generic template algorithm, (which I would be interested to see).

If you use specializations you can't use auto for the most part, the return types will have to be explicit. This should do what you want:

#include<cassert>
#include<array>

using std::array;

template<std::size_t Dim>
array<double, Dim*(Dim-1)/2> 
Cross(array<double, Dim> const&, array<double, Dim> const&);

template<> array<double, 0> Cross(
    array<double, 1> const&, array<double, 1> const& v2
){return {};}

template<> array<double, 1> Cross(
    array<double, 2> const& v1, array<double, 2> const& v2
){return {{v1[0] * v2[1] - v1[1] * v2[0]}};}

template<> array<double, 3> Cross(
    array<double, 3> const& v1, array<double, 3> const& v2
){
    return {{
        v1[1] * v2[2] - v1[2] * v2[1], 
        v1[2] * v2[0] - v1[0] * v2[2], 
        v1[0] * v2[1] - v1[1] * v2[0]
    }};
}

int main(){
    array<double, 1> v1{{1.}};
    array<double, 2> v2{{1.,2.}};
    array<double, 3> v3{{1.,2., 3.}};

    assert(( Cross(v1, v1) == array<double, 0>{} ));
    assert(( Cross(v2, v2)[0] == 0. ));
    assert(( Cross(v3, v3)[0] == 0. ));
}

Now at this point, if you know you are going to work with (total) specializations (like the above), there is little difference between this and separate overloads of Cross (except enforcing a certain return type defined in 6th line of code.)

like image 36
alfC Avatar answered May 30 '26 09:05

alfC