Russian Qt Forum

Программирование => С/C++ => Тема начата: Racheengel от Сентябрь 18, 2016, 13:02



Название: Специализация шаблона для базового класса
Отправлено: Racheengel от Сентябрь 18, 2016, 13:02
Всем привет,

понадобилась довольно простая вещь.
Скажем, есть класс A, и есть шаблон типа

Код:
template<class A, class X>
class T
...

со временем выяснилось, что некоторые наследники от класса A имеют некоторые различные методы.
Например, что то типа этого:

Код:
class B: public A
{
...
void methodB();
};

class C: public A
{
...
void methodC1();
virtual void methodC2();
};

class D: public C
{
...
virtual void methodC2();
};

В шаблоне есть несколько мест, где нужно, в зависимости от типа A, вызывать тот или иной метод наследника.
Типа такого:

Код:
void T<A,X>::doStuff()
{
// if A is B:
this->methodB();
return;

// else if A is D:
this->methodC2();   // тут надо вызвать метод из класса D...
return;

// else if A is C:
this->methodC2();   // тут надо вызвать метод из класса C...
}

Проблему можно решить через runtime с помощью dynamic_cast, и это будет работать 100%.
Вопрос, можно ли каким-то образом это решить через специализацию шаблона?
Или современный C++ все еще далек от элементарных вещей?

Пробовал через

Код:
template<class X>
void T<D,X>::doStuff()
{
this->methodC2();
}

что выглядит логичным с точки зрения разработчика (специализируем первый параметр шаблона классом D), но ни одна подобная конструкция не компилиццо :(


Название: Re: Специализация шаблона для базового класса
Отправлено: m_ax от Сентябрь 18, 2016, 13:52
Цитировать
Вопрос, можно ли каким-то образом это решить через специализацию шаблона?
Можно. И чтоб весь класс T не специализировать, достаточно завести хелпер, который будет возвращать нужный метод:

Код
C++ (Qt)
struct A
{
   void print() { cout << "A" << endl; }
};
 
struct B : public A
{
   void printB() { cout << "B" << endl; }
};
 
struct C : public A
{
   void printC() { cout << "C" << endl; }
};
 
 
template <class>
struct helper;
 
template<>
struct helper<A>
{
   static void method(A & obj) { obj.print(); }
};
 
template<>
struct helper<B>
{
   static void method(B & obj) { obj.printB(); }
};
 
template<>
struct helper<C>
{
   static void method(C & obj) { obj.printC(); }
};
 
 
template <class _A>
class T
{
public:
   void doStuff()
   {
       helper<_A>::method(m_obj);
   }
 
private:
   _A m_obj;
};
 
int main()
{
   T<A> objA;
   T<B> objB;
   T<C> objC;
 
   objA.doStuff();
   objB.doStuff();
   objC.doStuff();
 
   return 0;
}
 


Название: Re: Специализация шаблона для базового класса
Отправлено: Racheengel от Сентябрь 18, 2016, 15:27
Идею понял, спасибо :)
Получается, на КАЖДЫЙ метод КАЖДОГО класса, который будет параметром шаблона, создаем свой враппер-хелпер...

Немножко усложним задачку...
Предположим, что вызовов методов типа method...() мало.
Надо в doStuff() обработать результаты их выполнения и в зависимости от этого сделать то или иное действие.
Т.е. так:

Код:
// if this is A:
if (this->methodA() == BlaBla1)
{
  ... handle BlaBla1 case
  m_flags = SomeFlags1;
  ...
}
else
{
  ... handle BlaBla2 case
  if (m_flags & SomeOtherFlags)
    doAnotherStuff();
  else
    m_flags = SomeFlags2;
  ...
}

// if this is C
...
ну и в этом духе


Название: Re: Специализация шаблона для базового класса
Отправлено: m_ax от Сентябрь 18, 2016, 17:28
Цитировать
Получается, на КАЖДЫЙ метод КАЖДОГО класса, который будет параметром шаблона, создаем свой враппер-хелпер...
Не совсем.. Хелпер может содержать ни один метод - их там может быть несколько, в зависимости от задачи. Он просто задаёт интерфейс, а реализация для конкретного класса специализируется. Идея в том, что проще написать легковесные специализации хелпера, чем специализации самого класса T.

Цитировать
Немножко усложним задачку...
Предположим, что вызовов методов типа method...() мало.
Надо в doStuff() обработать результаты их выполнения и в зависимости от этого сделать то или иное действие.

И в чём проблема?


Название: Re: Специализация шаблона для базового класса
Отправлено: Igors от Сентябрь 18, 2016, 17:37
Немножко усложним задачку...
Предположим, что вызовов методов типа method...() мало.
Надо в doStuff() обработать результаты их выполнения и в зависимости от этого сделать то или иное действие.
Т.е. так:
Если очевидна/налицо "динамика", то зачем делать ее "статикой"? Приведетесь dynamic_cast<>, не облезете. 

И в чём проблема?
Видимо в том что нужны все хелперы в одном тельце, а не только один (специализированный)


Название: Re: Специализация шаблона для базового класса
Отправлено: m_ax от Сентябрь 18, 2016, 17:51
Цитировать
Видимо в том что нужны все хелперы в одном тельце, а не только один (специализированный)
Что значит все хелперы в одном тельце? Какие все?


Название: Re: Специализация шаблона для базового класса
Отправлено: Racheengel от Сентябрь 18, 2016, 18:32
Проблема в том, что в doStuff() нужно не только дергать методы в зависимости от типа _A, но и реагировать на их результаты в зависимости от типа _A.
При этом возможен доступ к приватным членам T, что через хелпер не получится (ну, только если их зафрендить, или сделать спец. интерфейс для доступа).
А хотелось бы просто такое:

Код:
void T<A>::doStuff()
{

switch (type of A)
{
case A:
  methodA();
...

case D:
  methodC();
  methodD();
...
}

}

Т.е. такой условный свитч, который "умно" инстанциирует части кода doStuff() в зависимости от того, кто есть A.
В принципе, нечто вроде сишного #ifdef... #endif, но для шаблонов.


Название: Re: Специализация шаблона для базового класса
Отправлено: Racheengel от Сентябрь 18, 2016, 18:51
Если очевидна/налицо "динамика", то зачем делать ее "статикой"? Приведетесь dynamic_cast<>, не облезете. 

dynamic_cast<> прекрасно работает (пока на нем родимом все и держится).
Но это все же runtime.
А главный плюс шаблонов - это "когда компилятор все разрулит", вроде бы.. ???
Внутри каждого инстанциированного класса динамика не нужна, т.к все зависит тока от типа A.
Вот решил воспользоваться фичей языка... а похоже, что без гемороюшки тут никак :(


Название: Re: Специализация шаблона для базового класса
Отправлено: m_ax от Сентябрь 18, 2016, 19:35
Цитировать
Вот решил воспользоваться фичей языка... а похоже, что без гемороюшки тут никак
Да нет, такой меташаблонный case вполне реализуется) Напишу, выложу)


Название: Re: Специализация шаблона для базового класса
Отправлено: m_ax от Сентябрь 18, 2016, 20:20
Вобщем вот примерный меташаблонный вариант swithа:

Код
C++ (Qt)
#include <iostream>
 
using namespace std;
 
struct A
{
   void print() { cout << "A" << endl; }
};
 
struct B : public A
{
   void printB() { cout << "B" << endl; }
};
 
struct C : public A
{
   void printC() { cout << "C" << endl; }
};
 
 
template <class>
struct helper;
 
template<>
struct helper<A>
{
   static void method(A & obj) { obj.print(); }
};
 
template<>
struct helper<B>
{
   static void method(B & obj) { obj.printB(); }
};
 
template<>
struct helper<C>
{
   static void method(C & obj) { obj.printC(); }
};
 
 
 
template <bool>
struct case_of_helper;
 
 
template <>
struct case_of_helper<true>
{
   template <class F>
   static void doStuff(F f) { f(); }
};
 
template <>
struct case_of_helper<false>
{
   template <class F>
   static void doStuff(F) {}
};
 
 
template <class Base, class Derived, class F>
void meta_case_of(F f)
{
   case_of_helper<std::is_same<Base, Derived>::value>::doStuff(f);
}
 
 
template <class Obj>
class T
{
public:
   void doStuff()
   {
       meta_case_of<A, Obj>([&]()
       {
           helper<Obj>::method(m_obj);
       });
 
       meta_case_of<B, Obj>([&]()
       {
           helper<Obj>::method(m_obj);
       });
 
       meta_case_of<C, Obj>([&]()
       {
           helper<Obj>::method(m_obj);
       });
   }
 
private:
   Obj m_obj;
};
 
 
int main()
{
   T<A> objA;
   T<B> objB;
   T<C> objC;
 
   objA.doStuff();
   objB.doStuff();
   objC.doStuff();
 
   return 0;
}
 
 

Здесь лямбда будет видеть все приватные члены и прочие методы..

P.S. Но к этому варианту нужно относиться только лишь как к некой технической возможности, не более.. Просто как к альтернативе..
Но это, на мой взгляд, плохое решение (хотя я и не знаю, Ваших замыслов и т.д.. ). Плохое в том смысле, что если появится новый наследник от класса A, то помимо специализации хелпера, дополнительно придётся лезть в код метода T<Obj>::doStuff.. Мне это кажется весьма сомнительным..


Название: Re: Специализация шаблона для базового класса
Отправлено: Racheengel от Сентябрь 18, 2016, 23:18
P.S. Но к этому варианту нужно относиться только лишь как к некой технической возможности, не более.. Просто как к альтернативе..
Но это, на мой взгляд, плохое решение (хотя я и не знаю, Ваших замыслов и т.д.. ). Плохое в том смысле, что если появится новый наследник от класса A, то помимо специализации хелпера, дополнительно придётся лезть в код метода T<Obj>::doStuff.. Мне это кажется весьма сомнительным..

Да, боюсь, на практике такая возможность будет несколько... скажем так... неадекватна по затратам по отношению к результатам :(
Гуглы и стековерфлоу я уже перекопал, там вообще все глухо.

Получается, что ни одна существующая реализация языка не поддерживает непосредственную инстанциацию фрагментов кода.
Хотя, опять же, как по мне - возможность была бы довольно мощной, а кроме того, не вижу проблем в имплементации для компилятора.
Статически выполнить сравнение типов параметров шаблона при инстанцировании и активировать нужные куски кода - это как то не особо сложно звучит, сишный препроцессор, не более.

В общем, "рантайм наше все", спасибо откликнувшимся :)


Название: Re: Специализация шаблона для базового класса
Отправлено: Igors от Сентябрь 19, 2016, 06:58
Получается, что ни одна существующая реализация языка не поддерживает непосредственную инстанциацию фрагментов кода.
Хотя, опять же, как по мне - возможность была бы довольно мощной, а кроме того, не вижу проблем в имплементации для компилятора.
"Запретный плод" всегда кажется "довольно мощным" :) Как я понимаю идея этих "хвостиков" (traits) в том что основной класс пишется один раз, а потом, отдельно дописываются хвосты для инстансов по мере их поступления. Так смысл есть (хотя конечно все равно мерзость). Но у Вас др ситуация - Вы хотите "куски кода", т.е. Вы уже согласны править основной класс всякий раз. Тогда чего затевать темплейты?

Кстати интересно, а напр при такой проверке
Код:
if (typeid(some_member) == typeid(some_class))
Не выкинет ли компилятор неиспользуемый код?

Да, боюсь, на практике такая возможность будет несколько... скажем так... неадекватна по затратам по отношению к результатам :(
Во-во-во, недавно в др теме (безуспешно) пытался объяснить то же самое


Название: Re: Специализация шаблона для базового класса
Отправлено: Old от Сентябрь 19, 2016, 08:50
понадобилась довольно простая вещь.
Racheengel, а у вас не возникает ощущения, что вы делаете что-то не то. :)
Как-то это уж очень топорно получается. Может стоит пересмотреть интерфейс у дерева классов или сделать не один шаблон, а несколько...
Сейчас для абстракной задачи сложно что-то посоветовать, но я бы такое в боевой код не тащил, даже если бы C++ это позволял. :)


Название: Re: Специализация шаблона для базового класса
Отправлено: __Heaven__ от Сентябрь 19, 2016, 11:04
Быть может есть возможность в классе A написать чистый виртуальный метод doStuff, который исполнит желаемое...


Название: Re: Специализация шаблона для базового класса
Отправлено: __Heaven__ от Сентябрь 19, 2016, 11:14
Быть может даже и виртуальность не нужна. Обычная перегрузка doStuff поможет?


Название: Re: Специализация шаблона для базового класса
Отправлено: Racheengel от Сентябрь 19, 2016, 11:14
Основная идея (проблема) в следующем.
Есть закрытая гуй-библиотека, которая содержит разные графические классы.
Некоторые из них имеют общего предка, некоторые нет.
У меня задача расширить функциональность всех этих классов одинаковым образом.
В настоящее время это сделано так, что параллельно к библиотечным существуют "свои" расширяющие классы.
Для того, чтого они могли сосуществовать с гуй-системой, они обязаны быть наследниками библиотечных объектов.
Решено это с помощью multiple inheritance: "свои" классы наследуются от "расширителей" и от гуй-объектов одновременно.
Т.е. так:

// это гуй-библиотека
class GuiBase;
class GuiObjectA : public GuiBase;
class GuiObjectB : public GuiObjectB;
class GuiObjectC : public GuiBase;
---

// это наши "расширители"
class ItemBase;
class ExtItem1: public ItemBase;
class ExtItem2: public ItemBase;
---

// это "расширенные" гуй-классы (идея)
class GuiItemA:  public GuiObjectA, public ExtItem1;
class GuiItemB:  public GuiObjectB, public ExtItem1;
class GuiItemA2:  public GuiObjectA, public ExtItem2;
class GuiItemC2:  public GuiObjectC, public ExtItem2;
и т.д.

проблема в том, что "расширители" должны иметь доступ к гуи-объектам (что при идее выше не будет работать).
Поэтому был слелан соответсвующий темлейт типа

template <class Gui, class Extender>
class GuiItem: public Gui, public Extender
// тут общий функционал, связывающий Gui и Extender

и далее

class GuiItemA:  public GuiItem<GuiObjectA, ExtItem1>;
class GuiItemC2:  public GuiItem<GuiObjectC, ExtItem2>;
...

в некоторых случаях имплементации GuiItem требуется различать между GuiObjectA и GuiObjectC.
Решено с помощью рантайма, хотя мне казалось, что тут как раз шаблоны - самое оно. Но как-то не срастается...


Название: Re: Специализация шаблона для базового класса
Отправлено: __Heaven__ от Сентябрь 19, 2016, 11:25
Накидал небольшой примерчик. Для вашей задачи вроде должен подойти. Но, вроде, тут надо идти другими путями.
https://ideone.com/zeBvoW
Код
C++ (Qt)
#include <iostream>
using namespace std;
 
struct A{
void print() const {cout << "A\n";}
};
 
struct B: public A{
void print() const {cout << "B\n";}
};
 
struct C: public B{
void print() const {cout << "C\n";}
};
 
template<class T>
void print(const T &someClass){
someClass.print();
}
 
int main() {
print(A());
print(B());
print(C());
 
return 0;
}
Цитировать
A
B
C


Название: Re: Специализация шаблона для базового класса
Отправлено: Old от Сентябрь 19, 2016, 11:29
проблема в том, что "расширители" должны иметь доступ к гуи-объектам (что при идее выше не будет работать).
Почему не будет работать?


Название: Re: Специализация шаблона для базового класса
Отправлено: Racheengel от Сентябрь 19, 2016, 11:40
проблема в том, что "расширители" должны иметь доступ к гуи-объектам (что при идее выше не будет работать).
Почему не будет работать?

Потому что, если написать так:

class GuiItemA:  public GuiObjectA, public ExtItem1;

то ExtItem1 не "видит", что такое GuiObjectA.
Это "знает" только GuiItemA. Но "знать" он должен в зависимости от того, что есть GuiObject...


Название: Re: Специализация шаблона для базового класса
Отправлено: Racheengel от Сентябрь 19, 2016, 11:42
Быть может даже и виртуальность не нужна. Обычная перегрузка doStuff поможет?

В том и проблема, что перегружать его надо внутри шаблона, в зависимости от его аргумента (GuiObjectX).
Я как раз этого и хотел добиться, но пока не срастаеццо :(


Название: Re: Специализация шаблона для базового класса
Отправлено: Old от Сентябрь 19, 2016, 11:47
то ExtItem1 не "видит", что такое GuiObjectA.
Это "знает" только GuiItemA. Но "знать" он должен в зависимости от того, что есть GuiObject...
Так может:
Код
C++ (Qt)
class GuiItemA:  public GuiObjectA, public ExtItem1<GuiObjectA>
 


Название: Re: Специализация шаблона для базового класса
Отправлено: Racheengel от Сентябрь 19, 2016, 11:55
Тогда проблема в том, что ExtItem1 будет темплейтом.
А в последующем коде повсеместно юзается dynamic_cast<ExtItem*>(...), что с темплейтом не прокатит :(


Название: Re: Специализация шаблона для базового класса
Отправлено: __Heaven__ от Сентябрь 19, 2016, 12:08
Racheengel, имхо, dynamic_cast можно виртуалами заменить. также, посмотрите такую конструкцию
Код
C++ (Qt)
template <class GuiItem>
class ExtendedGuiItemA:  public GuiItem
 
И мне кажется, что вы не посмотрели код в #16


Название: Re: Специализация шаблона для базового класса
Отправлено: Racheengel от Сентябрь 19, 2016, 14:43
Racheengel, имхо, dynamic_cast можно виртуалами заменить. также, посмотрите такую конструкцию
Код
C++ (Qt)
template <class GuiItem>
class ExtendedGuiItemA:  public GuiItem
 
И мне кажется, что вы не посмотрели код в #16

Посмотрел, это в принципе практически то же, что и m_ax предложил.
Проблема тут в том, что:
1. из someClass.print() нельзя просто получить доступ к приватным членам вызывающего класса.
2. не все классы имеют метод print(), для некоторых надо вызывать нечто иное.

Виртуалы, _возможно_, были бы решением. Правда, в них придется тащить кучу временных параметров...
Хотя неясно, как поведет себя компиль в таком случае:

class Base;
class Item: Base;
class Item2: Item;

virtual void doStuff(Base* base)
{
// код для Base
}

virtual void doStuff(Item2* item)
{
// код для Item2, но Item2 - тоже наследник Base :(
}

имхо тут конфликт будет...


Название: Re: Специализация шаблона для базового класса
Отправлено: m_ax от Сентябрь 19, 2016, 15:50
Цитировать
из someClass.print() нельзя просто получить доступ к приватным членам вызывающего класса.
Соберите тогда все приватные члены в один класс и передавайте объект этого класса в хелпер)


Название: Re: Специализация шаблона для базового класса
Отправлено: Igors от Сентябрь 19, 2016, 15:59
Основная идея (проблема) в следующем.
Есть закрытая гуй-библиотека, которая содержит разные графические классы.
Некоторые из них имеют общего предка, некоторые нет.
У меня задача расширить функциональность всех этих классов одинаковым образом.
В настоящее время это сделано так, что параллельно к библиотечным существуют "свои" расширяющие классы.
Для того, чтого они могли сосуществовать с гуй-системой, они обязаны быть наследниками библиотечных объектов.
Ну вообще-то это "фабрика" - создать новый класс из разных (или слабо-связных) исходных.


Название: Re: Специализация шаблона для базового класса
Отправлено: Old от Сентябрь 19, 2016, 17:29
Ну вообще-то это "фабрика" - создать новый класс из разных (или слабо-связных) исходных.
Нет. Фабрика это когда один объект создает другие объекты.


Название: Re: Специализация шаблона для базового класса
Отправлено: Racheengel от Сентябрь 19, 2016, 18:58
Цитировать
из someClass.print() нельзя просто получить доступ к приватным членам вызывающего класса.
Соберите тогда все приватные члены в один класс и передавайте объект этого класса в хелпер)

А методы? :) Хелперы френдить?


Название: Re: Специализация шаблона для базового класса
Отправлено: m_ax от Сентябрь 19, 2016, 19:23
Цитировать
А методы?  :) Хелперы френдить?
Так с таким раскладом здесь проще специализировать ваш T для каждого типа.. Если там всё так сильно друг на друге повязано..
Ну и вообще над архитектурой думать-думать и думать, а уже потом писать)


Название: Re: Специализация шаблона для базового класса
Отправлено: Racheengel от Сентябрь 19, 2016, 23:53
Ну и вообще над архитектурой думать-думать и думать, а уже потом писать)

Так уже все "придумано до нас" и изменениям не подлежит.
Наше дело - "расширить" :(


Название: Re: Специализация шаблона для базового класса
Отправлено: Racheengel от Сентябрь 20, 2016, 22:11
Попробовал вариант с хелпером... появилась такая проблема: допустим, у нас классы

class A;
class B;
class B1: public B;
class B2: public B;

template<>
class T<A>;

template<>
class T<B>;

Теперь, мне нужно инстанциировать все возможные T<B1>, T<B2>, и т.д.
Но компилятор говорит, что "нету специализаций для B1 и B2".
Это странно, т.к. я явно указал T<B> для базового класса. По идее, такие же правила должны действовать и для наследников ???


Название: Re: Специализация шаблона для базового класса
Отправлено: m_ax от Сентябрь 20, 2016, 23:17
Нет, T<B> и T<B1> - это разные типы с точки зрения языка..


Название: Re: Специализация шаблона для базового класса
Отправлено: Racheengel от Сентябрь 20, 2016, 23:42
Нет, T<B> и T<B1> - это разные типы с точки зрения языка..

Это довольно странно, ведь налицо возможность "статической виртуализации".
Для компилятора в принципе здесь нет проблемы неоднозначности.
Он видит, что для B1 нет специализации, идет вверх по иерархии, находит B и генерирует код...
По крайней мере так было бы логично, имхо.


Название: Re: Специализация шаблона для базового класса
Отправлено: ViTech от Сентябрь 21, 2016, 13:26
...
Теперь, мне нужно инстанциировать все возможные T<B1>, T<B2>, и т.д.
Но компилятор говорит, что "нету специализаций для B1 и B2".
Это странно, т.к. я явно указал T<B> для базового класса. По идее, такие же правила должны действовать и для наследников ???

Код
C++ (Qt)
struct A{ void print(){cout << "A" << endl;} };
struct B{ void print(){cout << "B" << endl;} };
struct B1: public B{ void print(){cout << "B1" << endl;} };
struct B2: public B{ void print(){cout << "B2" << endl;} };
 
template< class U, typename E = void >
struct T
{ void print(){cout << "T<U>" << endl;} };
 
template<>
struct T<A>
{ void print(){cout << "T<A>" << endl;} };
 
template<class U>
struct T<U, typename enable_if<is_base_of<B, U>::value>::type>
{ void print(){cout << "T<B>" << endl;} };
 
int main()
{
   T<A> t_a;
   T<B> t_b;
   T<B1> t_b1;
   T<B2> t_b2;
 
   t_a.print();
   t_b.print();
   t_b1.print();
   t_b2.print();
 
   return 0;
}
 

Но если уровней наследования больше, то какую специализацию использовать?

Код
C++ (Qt)
struct A{ void print(){cout << "A" << endl;} };
struct B{ void print(){cout << "B" << endl;} };
struct B1: public B{ void print(){cout << "B1" << endl;} };
struct B2: public B{ void print(){cout << "B2" << endl;} };
struct B21: public B2{ void print(){cout << "B21" << endl;} };
 
template< class U, typename E = void >
struct T
{ void print(){cout << "T<U>" << endl;} };
 
template<>
struct T<A>
{ void print(){cout << "T<A>" << endl;} };
 
template<class U>
struct T<U, typename enable_if<is_base_of<B, U>::value>::type>
{ void print(){cout << "T<B>" << endl;} };
 
template<class U>
struct T<U, typename enable_if<is_base_of<B2, U>::value>::type>
{ void print(){cout << "T<B2>" << endl;} };
 
int main()
{
   T<A> t_a;
   T<B> t_b;
   T<B1> t_b1;
   T<B2> t_b2;
   T<B21> t_b21;
 
   t_a.print();
   t_b.print();
   t_b1.print();
   t_b2.print();
   t_b21.print();
 
   return 0;
}
 


Название: Re: Специализация шаблона для базового класса
Отправлено: Racheengel от Сентябрь 21, 2016, 15:01
Цитировать
Но если уровней наследования больше, то какую специализацию использовать?

Ту, для которой специализирован ближайший предок.
то есть для B21 это будет B2.
Для В1 будет В.


Название: Re: Специализация шаблона для базового класса
Отправлено: ViTech от Сентябрь 21, 2016, 15:33
Ту, для которой специализирован ближайший предок.
то есть для B21 это будет B2.
Для В1 будет В.

Это в данном частном случае вы так решили :). Но в общем случае компилятору что делать? В С++ поддерживается множественное наследование, не всегда всё сводится к простым случаям "первый ближайший предок". И определить этого первого ближайшего предка, похоже, не так просто. В std type_traits я такого не встречал. Гуглить "c++ primary|direct base class trait".


Название: Re: Специализация шаблона для базового класса
Отправлено: Igors от Сентябрь 21, 2016, 15:49
Основная идея (проблема) в следующем.
Есть закрытая гуй-библиотека, которая содержит разные графические классы.
Некоторые из них имеют общего предка, некоторые нет.
У меня задача расширить функциональность всех этих классов одинаковым образом.
В настоящее время это сделано так, что параллельно к библиотечным существуют "свои" расширяющие классы.
Для того, чтого они могли сосуществовать с гуй-системой, они обязаны быть наследниками библиотечных объектов.
Решено это с помощью multiple inheritance: "свои" классы наследуются от "расширителей" и от гуй-объектов одновременно.
Т.е. так:

// это гуй-библиотека
class GuiBase;
class GuiObjectA : public GuiBase;
class GuiObjectB : public GuiObjectB;
class GuiObjectC : public GuiBase;
---

// это наши "расширители"
class ItemBase;
class ExtItem1: public ItemBase;
class ExtItem2: public ItemBase;
---

// это "расширенные" гуй-классы (идея)
class GuiItemA:  public GuiObjectA, public ExtItem1;
class GuiItemB:  public GuiObjectB, public ExtItem1;
class GuiItemA2:  public GuiObjectA, public ExtItem2;
class GuiItemC2:  public GuiObjectC, public ExtItem2;
и т.д.
До этого момента примерно понятно. Есть классы унаследованные от разных базовых (от своих +  из либы 1 + из либы 2)

проблема в том, что "расширители" должны иметь доступ к гуи-объектам (что при идее выше не будет работать).
Поэтому был слелан соответсвующий темлейт типа

template <class Gui, class Extender>
class GuiItem: public Gui, public Extender
// тут общий функционал, связывающий Gui и Extender

и далее

class GuiItemA:  public GuiItem<GuiObjectA, ExtItem1>;
class GuiItemC2:  public GuiItem<GuiObjectC, ExtItem2>;
...

в некоторых случаях имплементации GuiItem требуется различать между GuiObjectA и GuiObjectC.
Решено с помощью рантайма, хотя мне казалось, что тут как раз шаблоны - самое оно. Но как-то не срастается...
А тут как-то не очень, "нить теряется"

У меня задача расширить функциональность всех этих классов одинаковым образом.
Можно пример класса и метода что Вы хотите получить?


Название: Re: Специализация шаблона для базового класса
Отправлено: Racheengel от Сентябрь 21, 2016, 16:26
Но в общем случае компилятору что делать? В С++ поддерживается множественное наследование, не всегда всё сводится к простым случаям "первый ближайший предок". И определить этого первого ближайшего предка, похоже, не так просто.

А тут и не надо ничего решать. Достаточно следовать тем же правилам, которые используются при dynamic_cast.


Название: Re: Специализация шаблона для базового класса
Отправлено: Racheengel от Сентябрь 21, 2016, 16:41
Цитировать
Можно пример класса и метода что Вы хотите получить?

class A;
class B: public A;
class B1: public B;

template<class A>
class T
{
...
stuff();  // A::stuff();
...
}

template<>
class T<A>
{
stuff();  // вызвать A::stuff
}

template<>
class T<B>
{
stuff();  // вызвать B::stuff
}

А теперь хотим так:

class C: public T<B1>
{
stuff();  // поскольку B1 наследник от В, то вызвать B::stuff - но не работает, ибо "нет специализации для В1"
}


Название: Re: Специализация шаблона для базового класса
Отправлено: ViTech от Сентябрь 21, 2016, 17:41
А тут и не надо ничего решать. Достаточно следовать тем же правилам, которые используются при dynamic_cast.

Я тоже много всего хочу от С++ :).

Код
C++ (Qt)
class A;
class B;
 
class C : public A, public B;
class D: public C;
 
template<class U>
class T;
 
template<>
class T<A>;
 
template<>
class T<B>;
 
T<D> - ???
 

Что компилятору делать в этом случае? Даже в случае с dynamic_cast, к чему приводить будете? Наверняка тут и других особенностей хватает. Это вам в конкретном случае понадобилась специализация по первому предку. А другим в общем случае может не нужно такое поведение, и необходимо, чтобы использовалось общее определение шаблона. Как им тогда указывать, что нужно использовать общее определение, а не специализацию по первому предку?

Буквально на днях была статья на хабре в тему: Наследование реализаций: закопайте стюардессу (https://habrahabr.ru/post/310314/).

Когда приходится городить такое количество костылей, то проблема, скорей всего, лежит уровнем выше, в архитектуре. Понятно, что принимать стороннюю библиотеку приходится так, как она есть. И если ставится задача расширять коров с помощью коней, то без извращений не обойтись :).


Название: Re: Специализация шаблона для базового класса
Отправлено: Racheengel от Сентябрь 21, 2016, 18:23
Цитировать
Что компилятору делать в этом случае? Даже в случае с dynamic_cast, к чему приводить будете?

Ну тут все очевидно.
Если воспользоваться принципом dynamic_cast, получим такое:

T<D> d;

if (dynamic_cast<A*>(&d))
{
  // первая специализация - А - совпала ? тогда мы А :)
}
else if (dynamic_cast<B*>(&d))
{
  // а тут мы В
}
else
{
  // а тут ни А, ни В
}

Т.е. первая подходящая специализация, для D - это А, что соответствует порядку наследования.


Название: Re: Специализация шаблона для базового класса
Отправлено: ViTech от Сентябрь 21, 2016, 18:48
if (dynamic_cast<A*>(&d))
{
  // первая специализация - А - совпала ? тогда мы А :)
}
else if (dynamic_cast<B*>(&d))
{
  // а тут мы В
}
else
{
  // а тут ни А, ни В
}

Чем родитель "B" хуже родителя "A"? :) Но это лирика.

Можно попробовать вести параллельную иерархию с T:
Код
C++ (Qt)
struct A{ void print(){cout << "A" << endl;} };
struct B{ void print(){cout << "B" << endl;} };
struct B1: public B{ void print(){cout << "B1" << endl;} };
struct B2: public B{ void print(){cout << "B2" << endl;} };
struct B21: public B2{ void print(){cout << "B21" << endl;} };
 
template< class U, typename E = void >
struct T
{ void print(){cout << "T<U>" << endl;} };
 
template<>
struct T<A>
{ void print(){cout << "T<A>" << endl;} };
 
template<class U>
struct T<U, typename enable_if<is_base_of<B, U>::value>::type>
{ void print(){cout << "T<B>" << endl;} };
 
template<>
struct T<B2>
{ void print(){cout << "T<B2>" << endl;} };
 
template<>
struct T<B21> : public T<B2>{};
 
int main()
{
   T<A> t_a;
   T<B> t_b;
   T<B1> t_b1;
   T<B2> t_b2;
   T<B21> t_b21;
 
   t_a.print();
   t_b.print();
   t_b1.print();
   t_b2.print();
   t_b21.print();
 
   return 0;
}
 


Название: Re: Специализация шаблона для базового класса
Отправлено: Racheengel от Сентябрь 21, 2016, 20:42
Цитировать
Чем родитель "B" хуже родителя "A"?

Порядком наследования. Было бы
Код:
class C: public B, public A
то выигрывал бы В.

Цитировать
Можно попробовать вести параллельную иерархию с T:

Можно, но проблема в том, что не-темплейтные классы может создавать пользователь, а он понятия не имеет об иерархии T :(
поэтому мы и не можем специфицировать каждый наследник в отдельности...


Название: Re: Специализация шаблона для базового класса
Отправлено: Igors от Сентябрь 22, 2016, 08:42
Не въезжаю
template<class A>
class T
{
...
stuff();  // A::stuff();
...
}
Как зовется stuff? Ведь T не наследник аргумента A

template<>
class T<A>
{
stuff();  // вызвать A::stuff
}
Зачем специализация, чем не устраивал общий шаблон?


Название: Re: Специализация шаблона для базового класса
Отправлено: Racheengel от Сентябрь 22, 2016, 11:26
Извиняюсь, немного ошибся, должно быть так:

Код:
template<class A>
class T: public A
{
...
stuff();  // A::stuff();
...
}

Т.е. создаем наследника от А.
Специализация нужна для того, что не все А имеют одинаковые методы.
Например, для А надо вызвать A::stuff(), а для C надо и С::stuff(), и С::someAnotherStuff():

class C: public B
{
virtual void stuff();
virtual void someAnotherStuff();
};

Шаблон про someAnotherStuff() не знает, т.к. нет возможности проверить, что есть C при инстанциировании T<C>.


Название: Re: Специализация шаблона для базового класса
Отправлено: Igors от Сентябрь 22, 2016, 16:22
Т.е. создаем наследника от А.
Специализация нужна для того, что не все А имеют одинаковые методы.
Например, для А надо вызвать A::stuff(), а для C надо и С::stuff(), и С::someAnotherStuff():
Все равно цель от меня ускользает, нет четкого представления что нужно. Хорошо, про исходные классы Вы уже неск раз говорили, допустим все как-то получилось/связалось, как бы Вы хотели использовать готовое?


Название: Re: Специализация шаблона для базового класса
Отправлено: Racheengel от Сентябрь 22, 2016, 16:50
Если более конкретно, то речь о настройке параметров объектов.
В зависимости от того, кто есть А, а кто С, надо вызывать различные методы.
Темплейт - это некий аналог фасада.
Грубо говоря, у него есть метод updateParams(), торчащий наружу.
А уже внутри него надо реализовать вызовы различных методов из расширяемых классов, в зависимости от их типа.


Название: Re: Специализация шаблона для базового класса
Отправлено: Igors от Сентябрь 23, 2016, 11:13
А если сначала так
Код
C++ (Qt)
template<>
class T<A>
{
 pimpl->stuff();  // вызвать A::stuff
 
// data
A * pimpl;
};
А потом так
Код
C++ (Qt)
template <class A2, class A>
class Т2 : public A2, public T<A>
{
T2( void ) { pimpl = dynamic_cast<A *> (this); }
};


Название: Re: Специализация шаблона для базового класса
Отправлено: Racheengel от Сентябрь 23, 2016, 11:19
Вот именно так сейчас и сделано :)
Просто думалось, что темплейты это позволяют без свистоплясок порешать...


Название: Re: Специализация шаблона для базового класса
Отправлено: Igors от Сентябрь 23, 2016, 11:23
Вот именно так сейчас и сделано :)
Просто думалось, что темплейты это позволяют без свистоплясок порешать...
Не понял. Так они вроде и решают (ну почти без  :)). Чем же это решение "нешаблонно"?


Название: Re: Специализация шаблона для базового класса
Отправлено: Racheengel от Сентябрь 23, 2016, 11:55
Ну, оно то шаблонно, но dynamic_cast присутствует)
А была надежда, что можно избежать и этого...