Добрый день. После долго программирования ATL соорудил следующую конструкцию.
template<typename T>
struct Base
{
typename T::X m_x;
void foo()
{
((T *)this)->bar();
}
};
struct A : public Base<A>
{
typedef int X;
void bar()
{
}
};
По идее это обязывает в структуре A определить тип данных X, а переменная этого типа будет определена в предке. Использоваться m_x будет в потомке. Понимаю, что изврат, но это не компилируется. Функции потомка можно использовать при шаблонном наследовании, а типы — нет. Почему?
Здравствуйте, Аноним, Вы писали:
А>По идее это обязывает в структуре A определить тип данных X, а переменная этого типа будет определена в предке. Использоваться m_x будет в потомке. Понимаю, что изврат, но это не компилируется. Функции потомка можно использовать при шаблонном наследовании, а типы — нет. Почему?
Потому что у тебя циклическая зависимость между определениями классов. В каком порядке они по-твоему должны компилироваться?
Функции потомка таким образом (в определении класса) тоже нельзя использовать.
Здравствуйте, Аноним, Вы писали:
А>Добрый день. После долго программирования ATL соорудил следующую конструкцию. А>
А>template<typename T>
А>struct Base
А>{
А> typename T::X m_x;
А> void foo()
А> {
А> ((T *)this)->bar();
А> }
А>};
А>struct A : public Base<A>
А>{
А> typedef int X;
А> void bar()
А> {
А> }
А>};
А>
А>По идее это обязывает в структуре A определить тип данных X, а переменная этого типа будет определена в предке. Использоваться m_x будет в потомке. Понимаю, что изврат, но это не компилируется. Функции потомка можно использовать при шаблонном наследовании, а типы — нет. Почему?
Как мне кажется при таком синтаксисе компилятор просто создает форвард-зависимоть на тип. Поэтому использовать содержимое такого класса так же некорректно, как, например, вызывать ф-ции/обращаться членам к класса объявленного ч-з форвард декларацию до его поределения. Т.е.:
class A;
class B
{
...
void f(A *p_a)
{
p_a->foo(); // Ошибка, класс еще не описан, только объявлен
}
};
class A
{
public:
void foo()
{
...
}
};
R>Функции потомка таким образом (в определении класса) тоже нельзя использовать.
Наследование шаблонное. В ATL такое используется спошь и рядом. Выдержка из книги "ATL Internals":
Instead of polymorphism based on type compatibility, we have polymorphism based on signature compatibility. As long as the type has appropriate function signatures available, the compiler is perfectly happy. This kind of polymorphism has some interesting properties, of which ATL makes heavy use.
А>... A>Функции потомка можно использовать при шаблонном наследовании, а типы — нет. Почему?
На вопрос "почему" ответил remark. А исправить легко — для передачи типа X базовому классу просто добавь в его шаблон еще один параметр-тип:
template<typename T, typename X>
struct Base
{
X m_x;
X foo()
{
return ((T *)this)->bar();
}
};
struct A : public Base<A, int>
{
typedef int X;
X bar()
{
return X();
}
};
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, Аноним, Вы писали:
А>Добрый день. После долго программирования ATL соорудил следующую конструкцию. А>... А>По идее это обязывает в структуре A определить тип данных X, а переменная этого типа будет определена в предке. Использоваться m_x будет в потомке. Понимаю, что изврат, но это не компилируется. Функции потомка можно использовать при шаблонном наследовании, а типы — нет. Почему?
Этот паттерн называется CRTP.
Ошибку компиляции можно обойти например так:
template<typename T>
struct Base
{
struct inner
{
typename T::X m_x;
};
void foo()
{
((T *)this)->bar();
}
};
struct A : public Base<A>
{
typedef int X;
void bar()
{
}
};
R>На вопрос "почему" ответил remark. А исправить легко — для передачи типа X базовому классу просто добавь в его шаблон еще один параметр-тип:
Не канает. У тебя в шаблон фактически передается int, а не X. в А если X — это не int, а структура? У меня 1 класс Base, а классов A — куча и в каждом есть структура X, только у нее разное наполнение.
R>Очень опасная строчка:
Да, опасная. Однако, шаблон рассчитан на то, что класс, наследующийся от него, впишет себя в качестве параметра. Тут сложно ошибиться. Ну разве что если это будет не класс, а промежуточный шаблон.
Здравствуйте, rus-k, Вы писали:
RK>Да, опасная. Однако, шаблон рассчитан на то, что класс, наследующийся от него, впишет себя в качестве параметра. Тут сложно ошибиться. Ну разве что если это будет не класс, а промежуточный шаблон.
подумайте о виртуальных функциях и паттерне "шаблонный метод". возможно, это улучшит безопасность вашего кода.
Здравствуйте, rus-k, Вы писали:
R>>Функции потомка таким образом (в определении класса) тоже нельзя использовать. RK>Наследование шаблонное. В ATL такое используется спошь и рядом. Выдержка из книги "ATL Internals": RK>
RK>Instead of polymorphism based on type compatibility, we have polymorphism based on signature compatibility. As long as the type has appropriate function signatures available, the compiler is perfectly happy. This kind of polymorphism has some interesting properties, of which ATL makes heavy use.
Да, но ты (так же как и они) не можешь использовать определение класса derived в класса base<derived>, т.к. определение класса derived само зависит от определения base<derived> (наследование). Ты можешь использовать только forward declaration класса derived в base<derived>.
Помозгуй над следующим примером (будь готов, что мозг выпадет в осадок с переполнением стека):
template<typename derived_t>
class base
{
typedef derived_t::type type;
};
class derived : base<derived>
{
typedef base<derived>::type type;
};
Здравствуйте, rus-k, Вы писали:
R>>На вопрос "почему" ответил remark. А исправить легко — для передачи типа X базовому классу просто добавь в его шаблон еще один параметр-тип: RK>Не канает. У тебя в шаблон фактически передается int, а не X. в А если X — это не int, а структура? У меня 1 класс Base, а классов A — куча и в каждом есть структура X, только у нее разное наполнение.
Тебе придётся вынести структуры Х из классов А, и передавать вместо int их.
Иначе ты просишь абсолютно невозможного. Вот ещё один хороший пример, помимо того, который я привёл в соседней ветке:
template<typename derived_t>
struct base
{
derived_t m;
};
struct derived : base<derived>
{
};
Нету никакого теоретического способа как циклическая зависимость между определениями классов может работать.
Здравствуйте, rus-k, Вы писали:
R>>Иначе ты просишь абсолютно невозможного. Вот ещё один хороший пример, помимо того, который я привёл в соседней ветке: RK>Пример действительно жестяной
RK>Однако, с функциями прокатывает.
Я извиняюсь, но это бред. Оно ни с чем не может прокатывать.
Скорее всего ты просто с функциями пробуешь совершенно другой пример, например вызываешь из функции базового класса функцию производного класса. Это будет прекрасно работать и для типов — в функции базового типа ты можешь создать локальную переменную типа derived_t::X.
RK>К тому же в VC++ есть вот такая штука: http://msdn.microsoft.com/en-us/library/x7wy9xh3(VS.80).aspx.
Здравствуйте, remark, Вы писали:
R>Скорее всего ты просто с функциями пробуешь совершенно другой пример, например вызываешь из функции базового класса функцию производного класса. Это будет прекрасно работать и для типов — в функции базового типа ты можешь создать локальную переменную типа derived_t::X.
RK>Только не понятно, в чем разница. Так локальная переменная, так член класса...
Я могу повторить ещё раз, мне не сложно.
В первом случае у тебя циклическая зависимость между определениями классов.
Во втором случае циклической зависимости нет (тело функции-члена не является часть определения класса — оно вообще может быть вынесено в отдельную единицу трансляции).
Аноним:
А>Добрый день. После долго программирования ATL соорудил следующую конструкцию. А>
template<typename T>
struct Base
{
typename T::X m_x;
void foo()
{
((T *)this)->bar();
}
};
struct A : public Base<A>
{
typedef int X;
void bar()
{
}
};
А>По идее это обязывает в структуре A определить тип данных X, а переменная этого типа будет определена в предке. Использоваться m_x будет в потомке. Понимаю, что изврат, но это не компилируется. Функции потомка можно использовать при шаблонном наследовании, а типы — нет. Почему?
Здесь разница не в типах и функциях наследника, а в том, где именно ты используешь тип класса-наследника.
14.7.1/1:
Unless a class template specialization has been explicitly instantiated (14.7.2) or explicitly specialized (14.7.3), the class template specialization is implicitly instantiated when the specialization is referenced in a context that requires a completely-defined object type or when the completeness of the class type affects the semantics of the program. The implicit instantiation of a class template specialization causes the implicit instantiation of the declarations, but not of the definitions or default arguments, of the class member functions, member classes, static data members and member templates; and it causes the implicit instantiation of the definitions of member anonymous unions.
10/1:
The class-name in a base-specifier shall not be an incompletely defined class (clause 9)
14.6.4.1/3:
For a class template specialization, a class member template specialization, or a specialization for a class member of a class template, if the specialization is implicitly instantiated because it is referenced from within another template specialization, if the context from which the specialization is referenced depends on a template parameter, and if the specialization is not instantiated previous to the instantiation of the enclosing template, the point of instantiation is immediately before the point of instantiation of the enclosing template. Otherwise, the point of instantiation for such a specialization immediately precedes the namespace scope declaration or definition that refers to the specialization.
Отсюда можно сделать вывод, что специализация Base<A>, будучи указанной в качестве base-specifier, неявно инстанцируется, причём её точка инстанцирования находится сразу перед определением структуры A. Хотя не видно, чтоб об этом в стандарте было где-то прямо сказано, по идее, в точке инстанцирования специализации шаблона должен осуществляться полный анализ определения этой специализации — за исключением того, что автоматически неявно не инстанцируется (аргументы по умолчанию, mem-initializer-lists и тела функций-членов, определения вложенных классов, а также member templates).
Обращение к member-у класса возможно только там, где класс полностью определён, либо в пределах определения класса после точки объявления данного member-а. В точке инстанцирования Base<A> у класса A не может быть видно никаких member-ов.
template <class T>
struct B
{
B(int x = sizeof(typename T::type)) : // OK
m(sizeof(typename T::type)) // OK
{
typename T::type t; // OK
}
struct Nested
{
typename T::type m; // ill-formed when T becomes D
};
int m;
typename T::type t; // ill-formed when T becomes Dstatic int s;
};
template <class T>
int B<T>::s = sizeof(typename T::type);
// the point of instantiation for:
// struct B<D>, (see 14.6.4.1/3)
// struct B<D>::Nested (see 14.6.4.1/3)struct D : B<D>
{
typedef int type;
void f()
{
D().s += sizeof D::Nested().m;
}
};
// the point of instantiation for:
// expression sizeof(typename T::type) (see 14.6.4.1/2)
// ctor B<D>::B(int) (see 14.6.4.1/1)
// static data member B<D>::s (see 14.6.4.1/1)int main() {}
Здравствуйте, rus-k, Вы писали:
R>>На вопрос "почему" ответил remark. А исправить легко — для передачи типа X базовому классу просто добавь в его шаблон еще один параметр-тип:
RK>Не канает. У тебя в шаблон фактически передается int, а не X. в А если X — это не int, а структура? У меня 1 класс Base, а классов A — куча и в каждом есть структура X, только у нее разное наполнение.
Дык какя разница, передашь вместо int то, чем реально является X. Будь у тебя хоть десять куч классов А, все равно X в каждом A только один, и ты его знаешь на момент определения класса A. А чтобы избежать дублирования при определении типа A::X, лучше определение этого типа выполнить в Base, а в A его заюзать:
template<typename T, typename XT>
struct Base
{
typedef XT X;
X m_x;
};
struct A : public Base<A, int>
{
using Base::X; // Это объявление не является обязательным,
// если только класс A не шаблонный.
// Но я предпочитаю такие объявления делать
// в любом случае - так нагляднее - сразу понятно,
// где находится определение типа
X m_x;
};
--
Справедливость выше закона. А человечность выше справедливости.