Search code examples
c++operator-overloadingc++17variantvisitor-pattern

C++17 Best way to build operators with variant on two sides?


I have a project that use a std::variant for a variable length types :

using Length = std::variant<int, long, float, double, Fraction>;

The Fraction class already overload most of the operators.

I want to create arithmetics operators like :

Length operator+ (Length lhs, Length rhs);
Length operator- (Length lhs, Length rhs);
Length operator* (Length lhs, Length rhs);
Length operator/ (Length lhs, Length rhs);

But comparison operators too :

Length operator== (Length lhs, Length rhs);
Length operator!= (Length lhs, Length rhs);

Length operator>  (Length lhs, Length rhs);
Length operator>= (Length lhs, Length rhs);
Length operator<  (Length lhs, Length rhs);
Length operator<= (Length lhs, Length rhs);

This is the pattern I use to do the job with std::visit method.

Length operator* (Length lhs, Length rhs)
{
    Length res;

    std::visit([&res, rhs](auto& left)
    {
        using T = std::remove_cv_t<std::remove_reference_t<decltype(left)>>;

        if constexpr (std::is_same_v<T, int>)
        {
            std::visit([&res, left](auto& right)
            {
                using T = std::remove_cv_t<std::remove_reference_t<decltype(right)>>;

                if constexpr (std::is_same_v<T, int>)       { res = left * right; } else
                if constexpr (std::is_same_v<T, long>)      { res = left * right; } else
                if constexpr (std::is_same_v<T, float>)     { res = left * right; } else
                if constexpr (std::is_same_v<T, double>)    { res = left * right; } else
                if constexpr (std::is_same_v<T, Fraction>)  { res = left * right; }
            },
            rhs);
        } 
        else
        if constexpr (std::is_same_v<T, long>)
        {
            std::visit([&res, left](auto& right)
            {
                using T = std::remove_cv_t<std::remove_reference_t<decltype(right)>>;

                if constexpr (std::is_same_v<T, int>)       { res = left * right; } else
                if constexpr (std::is_same_v<T, long>)      { res = left * right; } else
                if constexpr (std::is_same_v<T, float>)     { res = left * right; } else
                if constexpr (std::is_same_v<T, double>)    { res = left * right; } else
                if constexpr (std::is_same_v<T, Fraction>)  { res = left * right; }
            },
            rhs);
        } 
        else
        if constexpr (std::is_same_v<T, float>)
        {
            std::visit([&res, left](auto& right)
            {
                using T = std::remove_cv_t<std::remove_reference_t<decltype(right)>>;

                if constexpr (std::is_same_v<T, int>)       { res = left * right; } else
                if constexpr (std::is_same_v<T, long>)      { res = left * right; } else
                if constexpr (std::is_same_v<T, float>)     { res = left * right; } else
                if constexpr (std::is_same_v<T, double>)    { res = left * right; } else
                if constexpr (std::is_same_v<T, Fraction>)  { res = left * right; }
            },
            rhs);
        } 
        else
        if constexpr (std::is_same_v<T, double>)
        {
            std::visit([&res, left](auto& right)
            {
                using T = std::remove_cv_t<std::remove_reference_t<decltype(right)>>;

                if constexpr (std::is_same_v<T, int>)       { res = left * right; } else
                if constexpr (std::is_same_v<T, long>)      { res = left * right; } else
                if constexpr (std::is_same_v<T, float>)     { res = left * right; } else
                if constexpr (std::is_same_v<T, double>)    { res = left * right; } else
                if constexpr (std::is_same_v<T, Fraction>)  { res = left * right; }
            },
            rhs);
        } 
        else
        if constexpr (std::is_same_v<T, Fraction>)
        {
            std::visit([&res, left](auto& right)
            {
                using T = std::remove_cv_t<std::remove_reference_t<decltype(right)>>;

                if constexpr (std::is_same_v<T, int>)       { res = left * right; } else
                if constexpr (std::is_same_v<T, long>)      { res = left * right; } else
                if constexpr (std::is_same_v<T, float>)     { res = left * right; } else
                if constexpr (std::is_same_v<T, double>)    { res = left * right; } else
                if constexpr (std::is_same_v<T, Fraction>)  { res = left * right; }
            },
            rhs);
        }
    },
    lhs);

    return res;
}

That works, but this is redundant, non esthetic, and probably not so fast to do large operation process.


Solution

  • Since your operands are of the variant type, just visit them both together:

    Length operator* (Length lhs, Length rhs)
    {
      return std::visit([](auto const &l, auto const &r) -> Length {
        return l * r;
      }, lhs, rhs);
    }
    

    The standard library already performs the required logic for you.