Search code examples
c++operator-overloadingfriendtemplate-specialization

c++ friend operator template specialization


I have a generalized modulo struct called quotient_ring. The relevant bits are shown below.

template <typename R = long long>
struct quotient_ring{
    using Q = quotient_ring;
    R x, m;

    ...

    template <typename T>
    friend constexpr std::basic_ostream<T> &operator<< (std::basic_ostream<T> &str, const Q &q){
        return str << '(' << q.x << ")%(" << q.m << ')';
    }
};

This operator << would print something like 2 mod 7 as (2)%(7). The reason I need the brackets is because the type R can become very nested. However, if R is only an arithmetic type, such as long long, I would like to print without the brackets. I found that one way to accomplish this is as follows.

template <typename T>
friend constexpr std::basic_ostream<T> &operator<< (std::basic_ostream<T> &str, const Q &q){
    if constexpr (std::is_arithmetic<R>::value) return str << q.x << '%' << q.m;
    else return str << '(' << q.x << ")%(" << q.m << ')';
}

I think this is a fine solution. However, I would like to know if the same can be achieved by means of template specialization. I tend to personally like template specialization more than branching on type traits.


Solution

  • I tend to personally like template specialization more than branching on type traits.

    Why? if constexpr is a compile-time branch. The equivalent SFINAE is far less readable.

    template <typename R = long long>
    struct quotient_ring{
        using Q = quotient_ring;
        R x, m;
    
        template <typename Char>
        friend constexpr std::enable_if_t<
            std::is_arithmetic_v<R>,
            std::basic_ostream<Char> &
        >
        operator<< (std::basic_ostream<Char> &str, const Q &q){
            return str << q.x << '%' << q.m;;
        }
    
        template <typename Char>
        friend constexpr std::enable_if_t<
            !std::is_arithmetic_v<R>,
            std::basic_ostream<Char> &
        >
        operator<< (std::basic_ostream<Char> &str, const Q &q){
            return str << '(' << q.x << ")%(" << q.m << ')';
        }
    };
    
    int main() {
        quotient_ring<quotient_ring<>> ring{
            {1, 2},
            {3, 4}
        };
        std::cout << ring << '\n'; // (1%2)%(3%4)
    }
    

    Might I suggest putting some spaces in the output (like (1 % 2) % (3 % 4)) to make it more readable?