2

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?

Jarod42
  • 203,559
  • 14
  • 181
  • 302
Simon
  • 405
  • 2
  • 8
  • 4
    As you note, `os << x;` is valid for some types, but not all the types you are using. You probably want to **start** by replacing that with a `stream(os, x);` function that you could make valid for all supported types. If you start with that, it may lead you to a much more specific question. – Drew Dormann Mar 31 '21 at 13:29
  • Related: https://stackoverflow.com/questions/5512910/explicit-specialization-of-template-class-member-function – G. Sliepen Mar 31 '21 at 13:29
  • 2
    A simple solution is to write private member functions to do the actual printing, and you can overload it with all the types you want to be able to print. Have `operator<<` forward to this function. It isn't the most flexible, but it is simple. – François Andrieux Mar 31 '21 at 13:29
  • 1
    Which version of C++ are you using? With C++17, you might use [`if constexpr` statements](https://en.cppreference.com/w/cpp/language/if), and with C++20 you can use [concepts](https://en.cppreference.com/w/cpp/concepts) to simplify this even more. – G. Sliepen Mar 31 '21 at 13:41
  • I'm using C++17. – Simon Mar 31 '21 at 14:03

1 Answers1

3

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

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • I had considered that, but using methods instead of free functions. In that case, the compiler still complains about a missing << operator for a rhs vector. Why are the free functions required for this to work? Or am I getting mixed up somehow? – Simon Mar 31 '21 at 14:02
  • Methods or .. `friend` functions? `friend` functions can only be found by "ADL" (if not declared elsewhere). – Jarod42 Mar 31 '21 at 14:08
  • Methods. I want to avoid "out-sourcing" the implementation of the class to free functions. Note that I want to treat vectors and scalars differently in some other methods, too, not just the friended operator. For example, Signal::real() should return the real part of the signal and needs to be slightly differently implemented depending on T. – Simon Mar 31 '21 at 14:18
  • Doesn't require lot of changes to have it as members [Demo](http://coliru.stacked-crooked.com/a/01ba93ba439927ab). Just care to use different template parameter. – Jarod42 Mar 31 '21 at 14:30
  • Aaaaah, I did change the template argument of the vector method, but kept the original argument of the scalar function. Of course the compiler would get confused by that. Thank you so much! – Simon Mar 31 '21 at 17:26