Allow scalar and vector types in a template

I am working on a class template for a signal processing library that is supposed to hold scalar samples or vectors of scalar samples (which is common in, e.g., frame-wise signal processing).

The class is essentially a facade for the std::vector with some added convenience methods and members.

For scalar types, everything is fine. But I am running into trouble when I allow vectors as template arguments. Here is a minimum example of what I mean:

template <class T>
class Signal
{
    public:
        Signal() = default;
        std::vector<T> samples;
           
        friend std::ostream& operator<<(std::ostream& os, const Signal& obj)
        {
            for (const auto& x : obj.samples)
            {
                os << x << ",";
            }
            return os << std::endl;
        }
}

I am obviously getting an error about a missing ‘<<‘ operator for a right-hand operand of type std::vector. What I want is to define different operators (and also some other methods not shown in the minimum example above) for scalar and vector-like template arguments.

I have done some research and learned about the concept of SFINAE, but I am having trouble connecting the dots.

Can anyone point me in the right direction? I feel like this should be a fairly common problem. Or am I going about this all wrong?

Answer

You might use overloads instead of SFINAE:

template <typename T>
std::ostream& print(std::ostream& os, const T& obj)
{
    return os << obj;
}

template <typename T>
std::ostream& print(std::ostream& os, const std::vector<T>& v)
{
    os << "{";
    const char* sep = "";
    for (const auto& e : v) {
        os << sep;
        print(os, e);
        sep = ", ";
    }
    return os << "}";
}

template <class T>
class Signal
{
public:
    Signal() = default;
    std::vector<T> samples;
       
    friend std::ostream& operator<<(std::ostream& os, const Signal& obj)
    {
        for (const auto& x : obj.samples)
        {
            print(os, x) << ",";
        }
        return os << std::endl;
    }
};

Demo