CRTP и шаблонные выражения

В сложной библиотеке, использующей шаблонные выражения и Curiously Recursive Template Pattern (CRTP), мне нужно, чтобы некоторые перегруженные операторы были специализированы с базовыми классами, но операции, включающие их производные классы, не находят специализацию базового класса.

Ie:

  • если оператор определен для BaseA‹ T > + BaseA‹ T >, то код DerivedA‹ T > + DerivedA‹ T > находит оператор без проблем.
  • если оператор определен для BaseB‹ T > + BaseB‹ T >, то код DerivedB‹ T > + DerivedB‹ T > находит оператор без проблем.
  • но если оператор определен для BaseB‹ BaseA‹ T > > + BaseB‹ BaseA‹ T > >, то DerivedB‹ DerivedA‹ T > > + DerivedB‹ DerivedA‹ T > > НЕ находит этот оператор.

Как я могу убедиться, что оператор для специализированного вложенного случая найден?

Я могу переформулировать проблему так:

Если у меня есть курсы (использующие CRTP)

template<typename derived, typename datatype> class BaseA;
template<typename derived, typename datatype> class BaseB;
template<typename datatype> class DerivedA : public BaseA<DerivedA<datatype>,datatype>;
template<typename datatype> class DerivedB : public BaseB<DerivedB<datatype>,datatype>;

и у меня есть оператор

template<class derived1, class derived2, class datatype>
operator+(const BaseB<derived1,datatype> &bb1,const BaseB<derived2,datatype> &bb2);

его можно использовать для решения функции DerivedB‹ DerivedA‹ double > > + DerivedB‹ DerivedA‹ double> >, например

DerivedB<DerivedA<double> > A;
DerivedB<DerivedA<double> > B;
A+B;

но если у меня есть более специализированный оператор для той же операции

template<class bderived1, class aderived1, class datatype, class bderived2, class aderived2>
operator+(const BaseB<bderived1,BaseA<aderived1,datatype> > &bb1,const BaseB<bderived2,BaseA<aderived2,datatype> > &bb2);

этот оператор не найден той же функцией

DerivedB<DerivedA<double> > A;
DerivedB<DerivedA<double> > B;
A+B;

Как я могу обеспечить поиск специализированного оператора для решения этой функции?

Я прикрепил мималистический код для воспроизведения проблемы, только одна строка BA1+BA2; не компилируется с g++.

Пример полного кода:

//uses templated expressions

//uses CRTP, see
//http://en.wikipedia.org/wiki/Curiously_Recurring_Template_Pattern
//http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Curiously_Recurring_Template_Pattern

//g++ problem.cpp -o problem

#include <iostream> //cout, endl
#include <stdlib.h> //EXIT_SUCCESS

using namespace std;


//TypeC
template<class datatype1, class datatype2>
class TypeC{

    public:
    TypeC(const datatype1 &d1,const datatype2 &d2){
        cout << "helloC" << endl;
    };
};

//BaseA
template <typename derived, typename datatype>
class BaseA{

};


//DerivedA
template <typename datatype>
class DerivedA : public BaseA<DerivedA<datatype>,datatype> {

};

//operator for BaseA+BaseA
template<class derived1, class derived2, class datatype>
TypeC< BaseA<derived1,datatype>,BaseA<derived2,datatype> >
operator+(const BaseA<derived1,datatype> &ba1,const BaseA<derived2,datatype> &ba2){
    return TypeC< BaseA<derived1,datatype>,BaseA<derived2,datatype> > (ba1,ba2);
};

//BaseB
template <typename derived, typename datatype>
class BaseB{

};


//DerivedB
template <typename datatype>
class DerivedB : public BaseB<DerivedB<datatype>,datatype> {


};

/*for reasons outside the scope of this example, operators for BaseB<> op BaseB<> need specialization, cant use the general case:
//operator for BaseB+BaseB
template<class derived1, class derived2, class datatype>
TypeC< BaseB<derived1,datatype>,BaseB<derived2,datatype> >
operator+(const BaseB<derived1,datatype> &bb1,const BaseB<derived2,datatype> &bb2){
    return TypeC< BaseB<derived1,datatype>,BaseB<derived2,datatype> > (bb1,bb2);
};
*/

//operator for BaseB<double>+BaseB<double>
template<class derived1, class derived2>
TypeC< BaseB<derived1,double>,BaseB<derived2,double> >
operator+(const BaseB<derived1,double> &bb1,const BaseB<derived2,double> &bb2){
    return TypeC< BaseB<derived1,double>,BaseB<derived2,double> > (bb1,bb2);
};

//operator for BaseB<BaseA>+BaseB<BaseA>
template<class derived1, class derived2, class Aderived1, class Aderived2, class datatype>
TypeC< BaseB<derived1,BaseA<Aderived1,datatype> >,BaseB<derived2,BaseA<Aderived2,datatype> > >
operator+(const BaseB<derived1,BaseA<Aderived1,datatype> > &bb1,const BaseB<derived2,BaseA<Aderived2,datatype> > &bb2){
    return TypeC< BaseB<derived1,BaseA<Aderived1,datatype> >,BaseB<derived2,BaseA<Aderived2,datatype> > > (bb1,bb2);
};




int main(int argc, char* argv[]){

    DerivedA<double> A1;
    DerivedA<double> A2;

    A1+A2; //knows this DerivedA+DerivedA is equivalent to BaseA+BaseA, hence calls "operator for BaseA+BaseA"

    DerivedB<double> B1;
    DerivedB<double> B2;

    B1+B2; //knows this DerivedB<double>+DerivedB<double> is equivalent to BaseB<double>+BaseB<double>,
    //hence calls "operator for BaseB<double>+BaseB<double>"

    DerivedB<DerivedA<double> > BA1;
    DerivedB<DerivedA<double> > BA2;

    BA1+BA2; //g++ error: no match for ‘operator+’ in ‘BA1 + BA2’
    //compiler cannot see this DerivedB<DerivedA<double> > + DerivedB<DerivedA<double> > is equivalent to BaseB<BaseA>+BaseB<BaseA>
    //I want it to see this op as equivalent to BaseB<derived1,BaseA<Aderived1,datatype> > + BaseB<derived2,BaseA<Aderived2,datatype> >
    //How can I make BaseA act as a wildcard for DerivedA and any other classes derived from it, in this nested case?

    return EXIT_SUCCESS;

}

person finlaylabs    schedule 31.08.2010    source источник
comment
этот пункт является проблемой? Неявные преобразования (пункт 4) будут выполняться для аргумента функции, чтобы преобразовать его в тип соответствующего параметра функции, если тип параметра не содержит параметров шаблона, которые участвуют в выводе аргумента шаблона.   -  person Chubsdad    schedule 31.08.2010
comment
Я не думаю, что понимаю пункт, так как я самоучка и довольно не полностью. Мне кажется, что перегрузка операторов шаблонными базовыми классами отлично работает для операций с операндами производного класса, если шаблонные операнды не являются вложенными. Это многое прекрасно компилируется. Но я хотел бы, чтобы он работал для производного класса, вложенного в другой производный класс, и я не вижу, как помочь компилятору распознать вложенные операнды производного класса как соответствующие перегрузке оператора вложенных базовых классов?   -  person finlaylabs    schedule 31.08.2010
comment
@finlaylabs: я все еще очень хорошо разбираюсь в шаблонах. Я думаю, нам нужно разобраться, почему 'r2' не разрешен в C++ BaseB‹DerivedB‹DerivedA‹double› ›, DerivedA‹double› › &r = BA1; BaseB‹DerivedB‹DerivedA‹double› ›, BaseA‹DerivedA‹double›, double› › &r2 = BA1;   -  person Chubsdad    schedule 31.08.2010
comment
Спасибо chubsdad за комментарии, я тоже не понимаю, почему «r2» запрещен. И если это запрещено, как это побороть?   -  person finlaylabs    schedule 01.09.2010
comment
@finlaylabs: Это связано с тем, что данный класс «A» и производный от него класс «B», а «X» является шаблоном функции, а некоторые «T», X‹T, B› не совпадают с X‹T, A›. То есть неявное преобразование не приводит к преобразованию «B» в «A», даже если «A» является основой «B».   -  person Chubsdad    schedule 01.09.2010
comment
@chubsdad: мне кажется, что, хотя неявные преобразования не происходят, решение функций производного класса «B» с помощью перегрузок операторов, определенных с использованием базового класса «A», работает, потому что по наследству «B» является «A». '. Это происходит для случаев A1+A2 и B1+B2 в моем коде. Каким-то образом использование перегруженных функций «базового класса» не работает для выражений «производного класса», хотя, когда типы вложены (используются в качестве параметров шаблона). Я попытался повторить проблему в родительском посте, не могли бы вы взглянуть?   -  person finlaylabs    schedule 02.09.2010


Ответы (1)


Это связано с тем, что тип аргумента DerivedB<DerivedA<double> > не является производным классом от BaseB<bderived1, BaseA<aderived1,datatype> >: аргументы operator+ имеют в качестве второго аргумента шаблона своего базового класса тип DerivedA<double> (то есть datatype), но параметр функции operator+ указывает BaseA<aderived1,datatype> в качестве второго аргумента шаблона.

Поскольку с таким количеством типов все довольно запутанно, давайте сделаем более простой пример.

template<typename T>
struct B { };

template<typename T>
struct D : B<T> { };

struct base { };
struct derived : base { };

Теперь посмотрим, как они себя поведут

template<typename T>
void f(B<T> const&);

void g(B<base> const&);
void h(B<derived> const&);

int main() {
  D<derived> dd;
  f(dd); // works, like your first case
  h(dd); // works too (just not deduced).

  g(dd); // does *not* work, just like your second case
}

Стандарт C++ указывает, что преобразования производного->базового значения учитываются при сопоставлении типа параметра выведенной функции. Вот почему file << "hello" работает: оператор определен в терминах basic_ostream<C,T>, хотя file действительно может быть basic_fstream (его производным классом). Это относится к вашему первому случаю. Но во втором случае вы пытаетесь вывести параметр, который полностью не является базовым классом переданного аргумента.

person Johannes Schaub - litb    schedule 03.09.2010