Andrew Tomazos
Andrew Tomazos

Reputation: 68738

partial template overload

Suppose I have a function that has a parameter that is overloaded by many different types. For example:

ostream& operator<<(ostream&, ...);

so if I have a class Foo

class Foo { ... }

I can define an overload

ostream& operator<<(ostream&, const Foo&);

and it works fine.

Now let's suppose I have a another class:

template<class T>
class vector {...}

And now I want to define an overload of the function that takes any vector<T> where T can be any valid type. Is this possible to do without defining it for all possible input parameters? What signature should I use for such a definition?

template<class T>
ostream& operator<<(ostream&, const vector<T>& v);

?

Background:

In this instance I actually do want to write an overload for std::vector:

ostream& operator<<(ostream&, const std::vector<T>& x);

to write out something like "{2, 4, 8}" or similiar, as my logging system is built on top of ostream and uses operator<< internally to "stringify" types.

I thought I would pose the question in general, but I should add the constraint that I cannot alter the class (std::vector for example) in this case.

Upvotes: 2

Views: 155

Answers (2)

Luc Danton
Luc Danton

Reputation: 35469

The usual solutions both rely on ADL. The first one is writing a function template, which looks just like in your question:

namespace ns {
    // vector must reside in this namespace

    template<typename T>
    std::ostream& operator<<(std::ostream& os, vector<T> const& v);
    // define somewhere
}

The other way doesn't require writing a template, but is intrusive because it's in the class definition:

namespace ns {
    template<typename T>
    class vector {
        /* stuff */
    public:
        /* we're using the injected name for the class,
           but vector<T> works just as well */
        friend
        std::ostream& operator<<(std::ostream& os, vector const&)
        {
            /* caveat: must be defined inline
               there's no other way to write the definitions for all T */
        }
    };
}

In both cases, client code looks like this:

std::ostream& os = /* a stream from somewhere */;
ns::vector<foo> v;
os << v; // adl picks up correct overload

You should probably use the first option, the second one is normally picked when implementing an operator for a class (i.e. you're writing vector in the first place). Note that due to the nature of namespaces you can reopen namespace ns to put your operator in here even if you're not the writer of ns::vector... except if it's the case that this is about namespace std, since only writing template specializations is allowed in some cases (and to clarify, function templates can't be partially specialized, and we can't use a total specialization here). Given that there is such a thing as std::vector, it may be the case that you're interested in the next last ditch option.


What if you want to add an overload or write a function template accepting a template from namespace std?

This is strictly my opinion, but I think there's only one sane way to do that: put your functionality in the namespace of your choice (which is obviously not namespace std since, again, it's not allowed to put it there), and have client-code ask for that.

namespace stream_extensions {

    template<typename T>
    std::ostream& operator<<(std::ostream& os, std::vector<T> const& v);
    // define somewhere

}

Client code looks like:

std::ostream& os = /* stream from somewhere */;
std::vector<T> v;
// os << v; won't work, can't find relevant operator
using namespace stream_extensions;
os << v; // Okay, stream_extensions::operator<< is in scope

If the name of the namespace is well-chosen and restricted to only contain a limited number of things in it, I think such a using directive is a very reasonable choice. Consider a using namespace literals::chrono; which allows one to write e.g. cond.wait(24_ms); instead of cond.wait(std::chrono::milliseconds(24));.

The big caveat of this technique is that the functionality must be entirely new. This isn't a way to emulate partial specialization for function template: if there is a generic function template in namespace std then there is a very high risk that it will be either preferred through ADL or that you'll end up with an overload resolution ambiguity. The example here makes sense because there is no unrestricted template<typename T> std::ostream& operator<<(std::ostream& os, T const& t); (or a similar member version).

Upvotes: 4

Mankarse
Mankarse

Reputation: 40643

The signature that you gave is the correct one to use:

template<class T>
ostream& operator<<(ostream&, const vector<T>& v);

This is not a partial template specialization, but rather a base template function which will be matched by the usual function overload resolution rules.

Upvotes: 1

Related Questions