开发者

Overloaded operators & inheritance & templates (A formidable combination)

Greetings all.

I am writing some code using the Boost Units library and have run into a problem.

I have managed to abstract the problem from Boost code so you won't be looking through reams of boost template meta programming. Though I'm sure if you have experience with that it could help. Here is the reproduction:

class Base{};
class Derived : public Base
{
public:
  Derived(){}
  Derived(const Base &){}
};

class Q {};
class U
{
public:
  template< typename Y >
  Q operator * (Y)
  {
    Q r;
    return r;
  }
};

Base operator * (U, const Base &)
{
  Base r;
  return r;
}

int main(int argc, char **argv)
{
  Base myBase;
  U myU;
  Base myOtherBase = myU * myBase;
  Derived myDerived;
  Derived myOtherDerived =  myU * myDerived;
  return 0;
}

So the problem (specifically) is as follows: myU * myBase uses operator * (U, const Base &) and returns type of Base, all good so far. Whereas myU * myDerived insists on using generalised U::operator * (Y) and hence returns a Q, no good because I wanted a Base again.

Now, all classes other than Base and Derived are boost library classes so I cannot modify the members of U. How do I "beat" U::operator * (Y) for overload/template deduction/i开发者_运维知识库nstantiation, in this case, in an elegant and "solved once and for ever" manner.

I am using MSVC++ 2008 in case it is relevant to anyone.

Edit: Added a possible (quite likely) solution in answers


Using the following should fix your problem

Base myOtherDerived =  myU * (Base&)myDerived;
// or
Base myOtherDerived =  myU * static_cast<Base&>(myDerived);

instead of

Derived myOtherDerived =  myU * myDerived;

This is not what we could call a "clean solution" though. I'm trying to find a better way to do it.


First, the problem: The const Base& parameter of the operator* will always be a worse fit than the template parameter's exact fit because of conversion from Derived to Base.
Next, the solution: Provide overloaded operator* for every derived class. :(


If you have control over the ordering of the operators, you could define your operator* in the opposite direction, i.e.,

Base operator* (const Base& lhs, U rhs)
{
    Base r;
    return r;
}

Now if you tried

Derived myOtherDerived =  myDerived * myU;

it would not fit the template in class U, getting you around the issue of the template function in U overriding your own operator* function.


Interesting issue. Templates and inheritance don't really mix well, and since you cannot fix U it makes for an interesting challenge for sure.

I propose to trump the overload deduction mechanism :)

The simplest way would be to provide an overload for each Derived class. Obviously it's impractical.

Unless we could use a helper class for which the operator is written, and mix typedefs in there to make it transparent for the client.

template <typename T>
struct BaseT: Base
{
  typedef T Tag;
};

template <typename T>
BaseT<T> operator*(U, BaseT<T> const&) { return BaseT<T>(); }

This should be preferred (as an overload) for any BaseT<X> because it matches more precisely than the generic overload proposed.

struct DerivedTag {};

typedef BaseT<DerivedTag> Derived;

Tadaaaam :)

And since BaseT is a class, you can actually specialize it on specific Tag arguments to have exactly the members / other functions you wish, and it'll feel exactly the same to the client.

Full example on Ideone at http://ideone.com/ZIudh, let's hope you do not hit a VS 2008 bug ;)

struct Base {};

template <typename T>
struct BaseT: Base
{
  typedef T Tag;
};

struct DerivedTag {};
typedef BaseT<DerivedTag> Derived;

class Q {};
class U
{
public:
  template< typename Y >
  Q operator * (Y)
  {
    Q r;
    return r;
  }
};

Base operator * (U, const Base &)
{
  Base r;
  return r;
}

template <typename T>
BaseT<T> operator*(U, BaseT<T> const&) { return BaseT<T>(); }

int main(int argc, char **argv)
{
  Base myBase;
  U myU;
  Base myOtherBase = myU * myBase;
  Derived myDerived;
  Derived myOtherDerived =  myU * myDerived;
  return 0;
}


Just as I gave up trying to solve this last night the answer hit me like a brick. The Q typed result of U::operator*(Base) is conceptually the same as the Base typed result of operator*(U,Base), even though they are being represented by different Types. All I need to do is provide a constructor for Base ( const Q & ) that accepts type Q.

class Base
{
public:
Base(){}
Base(const Q &){}
};

This makes my example compile. Now to see if I can actually write the desired constructor in the real version (where a Base is actually a Base<Q,...> and the Q we accept is a Q<Base,...>).

Unfortunately, presenting a simplified abstract example of the problem made it harder to spot this solution, so I had quite an advantage. Thanks go out to all who chipped in with ideas / comments and answers.


Using SFINAE for C++03 (boost::is_base_of is from Boost.TypeTraits):

class U {
public:
    template<typename Y>
    typename boost::enable_if<
        !boost::is_base_of<Base, Y>::value,
        Q
    >::type
    operator*(Y)
    {
        Q r;
        return r;
    }
};

An alternative way to do it with C++0x (std::is_base_of is from <type_traits>):

class U {
public:
    template<
        typename Y,
        typename = typename std::enable_if<
            !std::is_base_of<Base, Y>::value
        >::type
    >
    Q
    operator*(Y)
    {
        Q r;
        return r;
    }
};

A quick test indicates that your example seems to work with SFINAE but be warned that the two operators must be overloads for this to work: if operator*(Y) is the only operator that appears in the overload resolution set (e.g. because of ADL or because of where operator*(U, Base const&) is declared) then SFINAE will make it disappear (as intended) but overload resolution will end without a candidate.


As Xeo pointed out, the above doesn't help. To hopefully redeem myself, here's one last possibility:

in the example, the member operator*(Y) is non-const. If, however, it were const like operator*(Y) const as good style recommends, then you could provide a better, non-const match that forwards to your operator*(U const&, Base const&). This is brittle however: if your code uses a U const& then you will stumble on the original compile error.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜