phantum12265
phantum12265

Reputation: 11

operator overloading in templated class with multiple types

I am trying to build a templated vector class that can be based on any arithmetic type and do math with them. So a Vec3<T> + Vec3<U> should be permitted. Here is my declaration:

template <typename T>
class Vec3 {
private:
    T _x, _y, _z;
public:
    static_assert(std::is_arithmetic_v<T>, "Type must be arithmetic");
    Vec3(T x, T y, T z);
    ~Vec3() = default;
    
    template <typename U, typename V>
    friend Vec3<T> operator+(Vec3<U> lhs, const Vec3<V>& rhs);    
};

I wrote two definitions for this function (same hpp file but further down), one if all input types are the same the output type should be the same. But if one of them is different the output depends: int+double-> double, double+int -> double, long+int -> long, ... you get the idea.

template <typename T>
Vec3<T> operator+(Vec3<T> lhs, const Vec3<T>& rhs) {
    return Vec3<T>(T x, T y, T z);
}

template <>
Vec3<double> operator+(Vec3<int> lhs, const Vec3<double>& rhs) {
    return Vec3<double>(double x, double y, double z);
}

template <>
Vec3<double> operator+(Vec3<double> lhs, const Vec3<int>& rhs) {
    return Vec3<double>(double x, double y, double z);
}
...

I thought I would overload the operator+ for every combination of permissible types. However I'm getting an error:

error: template-id 'operator+><' for 'Color3<double> operator+(Color3<int>, const Color3<double>&)' does not match any template declaration

What's the proper way to go about setting up any number of possible combinations of types? Thanks.

Upvotes: 0

Views: 116

Answers (2)

Ted Lyngmo
Ted Lyngmo

Reputation: 118097

If you want any two Vec3 based entities to be able to use operator+ you can add a friend that deduces the proper return type: Vec3<decltype(U{} + V{})>:

template <typename T>
class Vec3 {
public:
    static_assert(std::is_arithmetic_v<T>, "Type must be arithmetic");

    Vec3(T x, T y, T z) : _x(x), _y(y), _z(z) {}
    ~Vec3() = default;

    // matches any two Vec3<>s
    template <class U, class V>
    friend Vec3<decltype(U{} + V{})> operator+(const Vec3<U>& lhs,
                                               const Vec3<V>& rhs);

private:
    T _x, _y, _z;
};

template <class U, class V>
Vec3<decltype(U{} + V{})> operator+(const Vec3<U>& lhs, const Vec3<V>& rhs) {
    return {lhs._x + rhs._x, lhs._y + rhs._y, lhs._z + rhs._z};
}

Upvotes: 1

Useless
Useless

Reputation: 67872

This isn't a direct answer to the question as posed, but a demo of how easy this is to write if you organize your data into something compatible with ranges:

#include <array>
#include <functional>
#include <iostream>
#include <ranges>
#include <utility>

template <typename F, typename... R>
auto vsum(F&& f, R&& ...ranges)
{
    return std::views::zip_transform(
        std::forward<F>(f), std::forward<R>(ranges)...
    );
}

int main()
{
    std::array<int, 3> u{ 1, 2, 3 };
    std::array<double, 3> v{ 6.1, 6.2, 6.3 };

    for (auto e: vsum(std::plus{}, u, v))
        std::cout << e << '\n';
}

Compiler Explorer link.

You can add type constraints if you want, but vsum(std::plus{}, ...) works automatically for everything with a suitable operator+.

I made it variadic because it was actually less typing for a quick demo, but there's no problem fixing it to two range parameters. Obviously it also generalizes with no effort to any number of dimensions.

Upvotes: 1

Related Questions