2

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.

SmileyCraft
  • 257
  • 1
  • 10

1 Answers1

2

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?

Indiana Kernick
  • 5,041
  • 2
  • 20
  • 50
  • I think the reason I tend to like SFINAE more is because it keeps the implementations more separate. Also because the code becomes less nested. And am I wrong to assume that SFINAE is handled at compile time as well? But I must admit that in this case, I think I like if constexpr more. The SFINAE looks very overkill with such a simple method. Although I am very new to both if constexpr and SFINAE, so I'm still trying to figure out what's best. – SmileyCraft May 02 '19 at 14:30