views:

420

answers:

5

In a comparison operator:

template<class R1, class R2>
bool operator==(Manager<R1> m1, Manager<R2> m2) {
    return m1.internal_field == m2.internal_field;
}

Is there any way I could enforce that R1 and R2 must have a supertype or subtype relation? That is, I'd like to allow either R1 to be derived from R2, or R2 to be derived from R1, but disallow the comparison if R1 and R2 are unrelated types.

+1  A: 

If concepts would have been included in C++0x you might have been able to use them with a compiler that implement them (like gcc).

As it's not the case, the only alternative currently available to do what you want seem to be the Boost Concept Check library.

Klaim
+8  A: 

A trait you want might look like this:

template <typename B, typename D>
struct is_base_of // check if B is a base of D
{
    typedef char yes[1];
    typedef char no[2];

    static yes& test(B*);
    static no& test(...);

    static D* get(void);

    static const bool value = sizeof(test(get()) == sizeof(yes);
};

Then you just need a static assert of some sort:

// really basic
template <bool>
struct static_assert;

template <>
struct static_assert<true> {}; // only true is defined

#define STATIC_ASSERT(x) static_assert<(x)>()

Then put the two together:

template<class R1, class R2>
bool operator==(Manager<R1> m1, Manager<R2> m2)
{
    STATIC_ASSERT(is_base_of<R1, R2>::value || is_base_of<R2, R1>::value);

    return p1.internal_field == p2.internal_field;
}

If one does not derive from the other, the function will not compile. (Your error will be similar to "static_assert<false> not defined", and it will point to that line.)

GMan
Cunning. (Last line should be 'static_assert<false>' not 'static_cast<false>, right?)
dash-tom-bang
@dash: Oops, yeah. @gf: Thanks. :)
GMan
Beware of using static_assert as a name for anything. It is going to be a keyword in C++0x.
Noah Roberts
@Noah: Right, but then we won't need to make one in the first place. :)
GMan
An examination of the GNU <type_traits> header reveals that this is pretty much exactly how `is_comparable_to` is defined.
Will
@GMan that's fucking awesome. The best C++ code snippets are the shortest ones. Compile-time for the effing WIN.
wilhelmtell
@GMan: wow! templates are really powerful!! But what is `_static_assert_`?
Lazer
@Lazer: When I'm going to change it now, since we don't even need that. `_static_assert_` was the name of a variable. That variable type is only defined if the boolean value is true, so if it's false you get an error. It's a way of stopping compilation if a condition doesn't hold true. I'm taking the name out because a temporary will do, and is simpler. C++0x will have `static_assert` built in as a keyword.
GMan
@GMan: Also, `test` and `get` are just declared, they also need to be defined somewhere, don't they?
Lazer
@Lazer: Nope, since `sizeof` doesn't actually evaluate the expression. So it just needs to know the return type of the function to know what its type is.
GMan
@GMan: 1) `get(/* no void */)` can be used equivalently, since this is C++. 2) about `test(...)`, I understand that it catches all calls of test that do not fit the `test(B*)` syntax. But how does it work? Is this the variable arguments syntax or something else?? thanks!
Lazer
@Lazer: I prefer `void ` for consistency and easier reading. And 2 is indeed the variadic argument syntax. It has the lowest ordering, so it's used to ensure it's the last resort. (All type traits classes use it for the last resort.)
GMan
@GMan: thanks a lot!
Lazer
+14  A: 

You can use boost's typetraits (is_base_of), and boost's enable_if.

#include <boost/type_traits.hpp>
#include <boost/utility/enable_if.hpp>

template <class R1, class R2>
struct has_derived_base_relationship :
    boost::integral_constant<
        bool, boost::is_base_of<R1, R2>::value || boost::is_base_of<R2, R1>::value 
    >
{};

template<class R1, class R2>
typename boost::enable_if<has_derived_base_relationship<R1, R2>, bool>::type 
operator==(Manager<R1> m1, Manager<R2> m2) {
    return p1.internal_field == p2.internal_field;
}

On the other hand, why would operator== usage have more value with types of the same inheritance tree? Wouldn't it have to use double dispatch to achieve meaningful results?

UncleBens
+1 When in doubt, check boost.
Jurily
A: 
template<class T, class B> struct Derived_from {
 static void constraints(T* p) { B* pb = p; }
 Derived_from() { void(*p)(T*) = constraints; }
};

template<class R2, class R1>
bool test(R1& r1) {
 Derived_from<R1,R2>(); // accept if R1 is derived from R2
 return false;
}

class Base {
public:
 virtual ~Base() { }
};

class Derived : public Base {

};

class Other {

};

int _tmain(int argc, _TCHAR* argv[])
{
 Derived d;
 Other o;

 test<Base>(d); // OK
 test<Base>(o); // Fails in VC++ 2005

 return 0;
}

Credits go to http://www2.research.att.com/~bs/bs_faq2.html#constraints

smink
+1  A: 

I must admit, I don't see the motivation behind this, particularly if it requires writing shedloads of supporting code. For your operator:

template<class R1, class R2>
bool operator==(Manager<R1> m1, Manager<R2> m2) {
    return p1.internal_field == p2.internal_field;
}

to compile without a warning, both template parameter types must be capable of being parameters to the Manager template, and those types must have private members (I assume p1 & p2 should be m1 & m2) called internal_field. Given those constraints, what is the chance that this template function can be called by accident on the wrong type(s)?

anon
I intentionally obscured my intentions in the original question, in order to avoid side-tracking the discussion :) `Manager<R>` was a fictitious class name. The actual application was making a pointer-like object `index_ptr<R>` where the internal representation was a `size_t` index instead of a memory address. Comparing two pointers of unrelated type isn't very meaningful, so that's why I wanted to forbid it, but nothing about `size_t` would prevent it.
Will
@Will: I considered a similar situation a while ago. The tricky part is when you have multiple inheritance `Derived : Base1, Base2`. A `Base1*` and a `Base2*` are not obviously related.
MSalters