Определение типа данных в потомке
От: Аноним  
Дата: 22.04.10 09:58
Оценка:
Добрый день. После долго программирования 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 будет в потомке. Понимаю, что изврат, но это не компилируется. Функции потомка можно использовать при шаблонном наследовании, а типы — нет. Почему?
Re: Определение типа данных в потомке
От: remark Россия http://www.1024cores.net/
Дата: 22.04.10 10:15
Оценка:
Здравствуйте, Аноним, Вы писали:

А>По идее это обязывает в структуре A определить тип данных X, а переменная этого типа будет определена в предке. Использоваться m_x будет в потомке. Понимаю, что изврат, но это не компилируется. Функции потомка можно использовать при шаблонном наследовании, а типы — нет. Почему?


Потому что у тебя циклическая зависимость между определениями классов. В каком порядке они по-твоему должны компилироваться?
Функции потомка таким образом (в определении класса) тоже нельзя использовать.


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re: Определение типа данных в потомке
От: saf_e  
Дата: 22.04.10 10:21
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Добрый день. После долго программирования 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()
 {
 ...
 }
};
Re[2]: Определение типа данных в потомке
От: rus-k  
Дата: 22.04.10 11:39
Оценка:
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.

Re: Определение типа данных в потомке
От: rg45 СССР  
Дата: 22.04.10 13:12
Оценка:
Здравствуйте, Аноним, Вы писали:


А>...

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();
    }
};
--
Справедливость выше закона. А человечность выше справедливости.
Re: Маленькое замечание
От: rg45 СССР  
Дата: 22.04.10 13:21
Оценка:
Очень опасная строчка:
  ((T *)this)->bar();

С ее помошью легко можно привести к типу, вовсе не являющемуся потомком.


Гораздо безопаснее так:
  static_cast<T*>(this)->bar();

В таком случае от ошибки убережет компилятор.
--
Справедливость выше закона. А человечность выше справедливости.
Re: Определение типа данных в потомке
От: _Paul Россия  
Дата: 22.04.10 14:34
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Добрый день. После долго программирования 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()
    {
    }
};
Re[2]: Определение типа данных в потомке
От: _Paul Россия  
Дата: 22.04.10 14:36
Оценка:
Здравствуйте, _Paul, Вы писали:

_P>Здравствуйте, Аноним, Вы писали:


Был неправ, невнимательно посмотрел код... Думал там определение типа.
Re[2]: Определение типа данных в потомке
От: rus-k  
Дата: 23.04.10 05:49
Оценка:
R>На вопрос "почему" ответил remark. А исправить легко — для передачи типа X базовому классу просто добавь в его шаблон еще один параметр-тип:
Не канает. У тебя в шаблон фактически передается int, а не X. в А если X — это не int, а структура? У меня 1 класс Base, а классов A — куча и в каждом есть структура X, только у нее разное наполнение.
Re[2]: Маленькое замечание
От: rus-k  
Дата: 23.04.10 05:51
Оценка: :))
R>Очень опасная строчка:
Да, опасная. Однако, шаблон рассчитан на то, что класс, наследующийся от него, впишет себя в качестве параметра. Тут сложно ошибиться. Ну разве что если это будет не класс, а промежуточный шаблон.
Re[3]: Маленькое замечание
От: uzhas Ниоткуда  
Дата: 23.04.10 06:34
Оценка:
Здравствуйте, rus-k, Вы писали:

RK>Да, опасная. Однако, шаблон рассчитан на то, что класс, наследующийся от него, впишет себя в качестве параметра. Тут сложно ошибиться. Ну разве что если это будет не класс, а промежуточный шаблон.


подумайте о виртуальных функциях и паттерне "шаблонный метод". возможно, это улучшит безопасность вашего кода.
Re[3]: Определение типа данных в потомке
От: remark Россия http://www.1024cores.net/
Дата: 23.04.10 06:38
Оценка: 1 (1) :)
Здравствуйте, 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;
};



1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[3]: Определение типа данных в потомке
От: remark Россия http://www.1024cores.net/
Дата: 23.04.10 06:46
Оценка:
Здравствуйте, 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>
{
};

Нету никакого теоретического способа как циклическая зависимость между определениями классов может работать.


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[4]: Определение типа данных в потомке
От: rus-k  
Дата: 23.04.10 07:04
Оценка:
R>Иначе ты просишь абсолютно невозможного. Вот ещё один хороший пример, помимо того, который я привёл в соседней ветке:
Пример действительно жестяной

Однако, с функциями прокатывает. К тому же в VC++ есть вот такая штука: http://msdn.microsoft.com/en-us/library/x7wy9xh3(VS.80).aspx.

Жаль
Re[5]: Определение типа данных в потомке
От: remark Россия http://www.1024cores.net/
Дата: 23.04.10 07:23
Оценка:
Здравствуйте, rus-k, Вы писали:

R>>Иначе ты просишь абсолютно невозможного. Вот ещё один хороший пример, помимо того, который я привёл в соседней ветке:

RK>Пример действительно жестяной

RK>Однако, с функциями прокатывает.


Я извиняюсь, но это бред. Оно ни с чем не может прокатывать.
Скорее всего ты просто с функциями пробуешь совершенно другой пример, например вызываешь из функции базового класса функцию производного класса. Это будет прекрасно работать и для типов — в функции базового типа ты можешь создать локальную переменную типа derived_t::X.

RK>К тому же в VC++ есть вот такая штука: http://msdn.microsoft.com/en-us/library/x7wy9xh3(VS.80).aspx.


И?


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[6]: Определение типа данных в потомке
От: rus-k  
Дата: 23.04.10 08:32
Оценка: :)
Здравствуйте, remark, Вы писали:

R>Скорее всего ты просто с функциями пробуешь совершенно другой пример, например вызываешь из функции базового класса функцию производного класса. Это будет прекрасно работать и для типов — в функции базового типа ты можешь создать локальную переменную типа derived_t::X.


Действительно так.
template<typename T>
struct Base
{
    T::X m_x;        // Ошибка!!!
    void foo()
    {
        T::X m_x;    // Допустимо!!!
    }
};

Только не понятно, в чем разница. Так локальная переменная, так член класса...
Re[7]: Определение типа данных в потомке
От: remark Россия http://www.1024cores.net/
Дата: 23.04.10 08:40
Оценка:
Здравствуйте, rus-k, Вы писали:

RK>
RK>template<typename T>
RK>struct Base
RK>{
RK>    T::X m_x;        // Ошибка!!!
RK>    void foo()
RK>    {
RK>        T::X m_x;    // Допустимо!!!
RK>    }
RK>};
RK>

RK>Только не понятно, в чем разница. Так локальная переменная, так член класса...

Я могу повторить ещё раз, мне не сложно.
В первом случае у тебя циклическая зависимость между определениями классов.
Во втором случае циклической зависимости нет (тело функции-члена не является часть определения класса — оно вообще может быть вынесено в отдельную единицу трансляции).


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[8]: Определение типа данных в потомке
От: rus-k  
Дата: 23.04.10 09:03
Оценка:
Здравствуйте, remark, Вы писали:

R> тело функции-члена не является часть определения класса


О! Доперло. Спасибо.
Re: Определение типа данных в потомке
От: Masterkent  
Дата: 23.04.10 10:47
Оценка:
Аноним:

А>Добрый день. После долго программирования 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 D
    static 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() {}
Re[3]: Определение типа данных в потомке
От: rg45 СССР  
Дата: 23.04.10 12:03
Оценка:
Здравствуйте, 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;
};
--
Справедливость выше закона. А человечность выше справедливости.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.