Перегруженные операторы, наследование и шаблоны (грозная комбинация) - PullRequest
7 голосов
/ 14 мая 2011

Привет всем.

Я пишу код с использованием библиотеки Boost Units и столкнулся с проблемой.

Мне удалось абстрагировать проблему от кода Boost, чтобы вы не просматривали множество метапрограмм Boost Template. Хотя я уверен, что если у вас есть опыт, это может помочь. Вот репродукция:

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;
}

Итак, проблема (в частности) заключается в следующем: myU * myBase использует operator * (U, const Base &) и возвращает тип Base, пока все хорошо. Принимая во внимание, что myU * myDerived настаивает на использовании обобщенного U::operator * (Y) и, следовательно, возвращает Q, ничего хорошего, потому что я снова хотел Base.

Теперь все классы, кроме Base и Derived, являются классами расширенной библиотеки, поэтому я не могу изменить члены U. Как мне "разбить" U::operator * (Y) для перегрузки / дедукции / создания шаблона, в данном случае, в элегантной и «решенной раз и навсегда» манере.

Я использую MSVC ++ 2008 на тот случай, если он кому-нибудь нужен.

Редактировать: Добавлено возможное (вполне вероятное) решение в ответах

Ответы [ 6 ]

2 голосов
/ 14 мая 2011

Использование следующего должно решить вашу проблему

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

вместо

Derived myOtherDerived =  myU * myDerived;

Это не то, что мы могли бы назвать «чистым решением».Я пытаюсь найти лучший способ сделать это.

2 голосов
/ 14 мая 2011

Во-первых, проблема: параметр const Base& для operator* всегда будет хуже соответствовать точному соответствию параметра шаблона из-за преобразования из Derived в Base.
Далее решение:Укажите перегруженный operator* для каждого производного класса.(

1 голос
/ 14 мая 2011

Интересный вопрос.Шаблоны и наследование не очень хорошо сочетаются, и, поскольку вы не можете исправить U, это наверняка создаст интересную проблему.

Я предлагаю превзойти механизм удержания перегрузки:)

Простейшим способом было бы обеспечить перегрузку для каждого Derived класса.Очевидно, что это непрактично.

Если мы не сможем использовать вспомогательный класс, для которого написан оператор, и смешать там typedefs, чтобы сделать его прозрачным для клиента.

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

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

Это должно быть предпочтительным(как перегрузка) для любого BaseT<X>, потому что он соответствует более точно, чем предложенная общая перегрузка.

struct DerivedTag {};

typedef BaseT<DerivedTag> Derived;

Tadaaaam:)

И поскольку BaseT является классом, вы можетефактически специализируйте его на конкретных аргументах Tag, чтобы иметь именно те члены / другие функции, которые вы хотите, и он будет точно таким же для клиента.

Полный пример для Ideone на http://ideone.com/ZIudh, будем надеяться, что выне нажимайте на ошибку VS 2008;)

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;
}
1 голос
/ 14 мая 2011

Если у вас есть контроль над порядком операторов, вы можете определить operator* в обратном направлении, то есть

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

Теперь, если вы пытались

Derived myOtherDerived =  myDerived * myU;

это не будет соответствовать шаблону в классе U, что поможет вам решить проблему с функцией шаблона в U, переопределяющей вашу собственную operator* функцию.

0 голосов
/ 14 мая 2011

Как только я отказался от попыток решить эту проблему прошлой ночью, ответ ударил меня, как кирпич.Q типизированный результат U::operator*(Base) концептуально совпадает с Base типизированным результатом operator*(U,Base), даже если они представлены различными типами.Все, что мне нужно сделать, это предоставить конструктор для Base ( const Q & ), который принимает тип Q.

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

Это делает мой пример компиляцией.Теперь посмотрим, смогу ли я написать нужный конструктор в реальной версии (где Base на самом деле Base<Q,...>, а принимаемый Q - Q<Base,...>).

К сожалению,упрощенный абстрактный пример проблемы усложнил поиск этого решения, поэтому у меня было немало преимуществ.Спасибо всем, кто принял участие в обсуждении идей / комментариев и ответов.

0 голосов
/ 14 мая 2011

Использование SFINAE для C ++ 03 (boost::is_base_of из 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;
    }
};

Альтернативный способ сделать это с C ++ 0x (std::is_base_of от <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;
    }
};

Быстрый тест показывает, что ваш пример, кажется, работает с SFINAE, но имейте в виду, что для работы этих двух операторов должны быть перегружены: если operator*(Y) - единственный оператор, который появляется в наборе разрешения перегрузки (например, из-за ADL или из-за того, где объявлено operator*(U, Base const&), тогда SFINAE заставит его исчезнуть (как предполагалось), но разрешение перегрузки закончится без кандидата.


Как указал Ксео, вышесказанное не помогает. Надеюсь, выкупить себя, вот еще одна последняя возможность:

в этом примере член operator*(Y) не является константным. Однако, если это было бы как operator*(Y) const, как рекомендует хороший стиль, то вы могли бы обеспечить лучшее, неконстантное совпадение, которое перешло бы к вашему operator*(U const&, Base const&). Однако это хрупко: если ваш код использует U const&, вы наткнетесь на исходную ошибку компиляции.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...