What's the syntax to overload operator== as a free function with templated parameters?
Asked Answered
K

3

5

I have a set of polymorphic classes, such as:

class Apple {};
class Red : public Apple {};
class Green : public Apple {};

And free functions which compare them:

bool operator==(const Apple&, const Apple&);
bool operator< (const Apple&, const Apple&);

I'm designing a copyable wrapper class which will allow me to use classes Red and Green as keys in STL maps while retaining their polymorphic behaviour.

template<typename Cat>
class Copy
{
public:
    Copy(const Cat& inCat) : type(inCat.clone()) {}
    ~Copy() { delete type; }
    Cat* operator->() { return type; }
    Cat& operator*() { return *type; }
private:
    Copy() : type(0) {}
    Cat* type;
};

I want the Copy<Apples> type to be as interchangeable with Apples as possible. There are a few more functions I'll have to add to the Copy class above, but for now I'm working on a free function for operator==, as follows:

template<typename Cat>
bool operator==(const Copy<Cat>& copy, const Cat& e) {
    return *copy == e;
}

Here is part of my testing code:

Red red;
Copy<Apple> redCopy = red;
Copy<Apple> redCopy2 = redCopy;
assert(redCopy == Red());

But the compiler is telling me

../src/main.cpp:91: error: no match for ‘operator==’ in ‘redCopy == Red()’

How do I get it to recognize my operator== above? I suspect the answer might be in adding some implicit conversion code somewhere but I'm not sure what to do.

Kristie answered 6/6, 2011 at 19:51 Comment(5)
Just think how nice it would be if I could select all your code, paste it into an editor and compile it. But I can't. So I won't.Colet
@Neil: "Real programmers" don't need something as pedestrian as a compiler to spot errors. They can just Use the Source... I mean, er, read the source.Receipt
@High Yup, I can do that, but not when it is interspersed with pointless text. However, at the end of the day the compiler is the final arbiter, so why not make life easy for it?Colet
All questions would be better if they were a block of code, with no context or explantion. Shame on you, Kyle. Shame.Oshiro
@nbt "..the compiler is the final arbiter" - in most of programming languages it is true that the final truth is only known to compiler, not to the programmer. However in C++ it is more like you and compiler write/read two different programs from single source file and usually, however, compiler wins, because C++ grammar is undecidable and humans are not that good oracles :)Feodor
R
9

Your template is declared as

template <typename Cat>
bool operator==(const Copy<Cat>& copy, const Cat& e)

This doesn't match redCopy == Red() because Red() is of type Red, so the compiler deduces Red as the type of the second argument, i.e. Cat = Red, but then it expects the type of the first argument to be Copy<Red>, which it is not (redCopy's type is Copy<Apple>).

What you really want to express is something like

template <typename Cat>
bool operator==(const Copy<Cat>& copy, const something-that-derives-from-Cat& e)

The easiest way to do this, is to add a second template parameter:

template <typename Cat, typename DerivedFromCat>
bool operator==(const Copy<Cat>& copy, const DerivedFromCat& e)

Of course, this doesn't get the compiler to enforce that DerivedFromCat is actually derived from Cat. If you want this, you can use boost::enable_if:

template <typename Cat, typename DerivedFromCat>
typename enable_if<is_base_of<Cat, DerivedFromCat>, bool>::type
operator==(const Copy<Cat>&, const DerivedFromCat& e)

But that may be a bit of overkill...

Receipt answered 6/6, 2011 at 20:2 Comment(1)
You want is_base_of, not is_base_and_derived. The latter is an implementation detail of the formerNudge
H
3

But... How do you expect it to work? You declared a template operator

template<typename Cat>
bool operator==(const Copy<Cat>& copy, const Cat& e)

meaning that the type on the RHS is the same as template argument on the LHS (Cat in both cases). Yet you expect it to be called in case of

redCopy == Red()

where redCopy is Copy<Apple>. How?

Note: the template argument for redCopy is Apple, not Red. Your template operator simply can't possibly match these types.

If you had your redCopy declared as

Copy<Red> redCopy;

then your operator would work. Or if you did

redCopy == Apple()

your operator would work as well. But when you mix types like your original

Copy<Apple> redCopy;
redCopy == Red();

it simply can't work. What is your intent in this case?

Hysterectomy answered 6/6, 2011 at 20:3 Comment(0)
H
3

@HighCommander4 explained what is wrong here. An alternative solution is to disable deduction for the second parameter of operator==. The type of the second parameter is then deduced solely based on the first argument of the ==-operator:

template<typename T> struct identity { typedef T type; };

template<typename Cat>
bool operator==(const Copy<Cat>& copy, typename identity<Cat>::type const& e) {
    return *copy == e;
}

If you do it like this, there is no contradiction as to what type Cat is supposed to stand for, and the operator== will work as expected.

Hartill answered 6/6, 2011 at 20:30 Comment(2)
I think the only use for the identity template I've seen has been to allow long long(5) to become identity<long long>::type(5). This is much less worthless.Oshiro
@Dennis I'm surely going to propose template<typename T> using nondeduced = typename identity<T>::type; for the after-C++0x C++ revision if no-one else does it. It then just becomes nondeduced<Cat> const& e. That looks so much cuter with alias templates.Hartill

© 2022 - 2024 — McMap. All rights reserved.