Как объявить член в базовом шаблонном классе, где тип зависит от производного класса?

Учитывая базовый класс с использованием CRTP, я смотрю на объявление члена в базовом шаблонном классе, где тип зависит от производного класса.

Пока следующее работает как задумано:

template <class T> class BaseTraits;
template <class T> class Base {
using TypeId = typename BaseTraits<T>::TypeId;
TypeId id;
public:
Base() { id = 123; }
TypeId getId() { return id; }
};

class Derived;
template <> class BaseTraits<Derived> {
public:
using TypeId = int;
};

class Derived : public Base<Derived> {};

int main(int argc, char ** argv) {
Derived foo;
return foo.getId();
}

Интересно, смогу ли я упростить реализацию? Я мог бы добавить второй параметр шаблона в Base шаблон и сделать BaseTraits проще или даже избавиться от этого. Однако приведенный выше фрагмент уже является попыткой удалить второй параметр шаблона. Я смотрю на решения, которые не включают второй параметр шаблона для Base,

Я пробовал что-то вроде следующего, но он не компилируется:

ошибка: недопустимое использование неполного типа ‘class Derived’

template <class T> class Base {
using TypeId = typename T::TypeId;
TypeId id;
public:
Base() { id = 123; }
TypeId getId() { return id; }
};

class Derived : public Base<Derived> {
public:
using TypeId = int;
};

int main(int argc, char ** argv) {
Derived foo;
return foo.getId();
}

ОБНОВИТЬ:

  • Я ограничен C ++ 14.
  • Base должен быть шаблоном.
  • Производительность является обязательным.

4

Решение

Можно ли сделать тип члена непосредственно зависит от производного класса? Принимая appart тип результата функции-члена, объявленной с auto (выведенный тип возврата), это невозможно.

Так что использование типа черта как вы делаете в Ваше решение — лучшее и единственное решение.

Причина в том, что базовый класс должен быть завершенным типом, когда определяется производный класс: компилятор должен сначала создать экземпляр и проанализировать определение базового класса, прежде чем он проанализирует определение производного класса, Стандарт C ++ N4140 [производный.класс] / 2 (жирный мой):

Тип, обозначаемый спецификатором базового типа, должен быть типом класса, который не полностью определенный класс[…]

3

Другие решения

Как насчет чего-то вроде этого:

template <typename T, typename TypeId> class Base
{
private:
TypeId id;
public:
Base() { id = 123; }
TypeId getId() {return id;}
};

class Derived : public Base<Derived, int> {};
2

Это немного упрощенно, но вы платите за это определенную цену.

#include <any>

template <class T> class Base {
std::any id; // expensive, but cannot have T::TypeId here
public:
Base() : id(123) {}
auto getId() {
return std::any_cast<typename T::TypeId>(id);
} // T::TypeId is OK inside a member function
};

class Derived : public Base<Derived> {
public:
using TypeId = int;
};
1

Почему бы не изменить иерархию классов?

template <class T>
class Base : T {
using TypeId = typename T::TypeId;
TypeId id;
public:
Base() { id = 123; }
TypeId getId() { return id; }
};

struct BasicDerived {
using TypeId = int;
};using Derived = Base<BasicDerived>;
1

Если честно, вы попали в стену жестких круговых зависимостей.
Любой выход будет вонючим.
В итоге два аргумента шаблона кажутся небольшой ценой.

Не могли бы вы объявить фиктивный класс шаблона, который принимает Derived и TypeID? Я не думаю, что это приносит вам пользу, хотя.

Является ли TypeID: производным сопоставления 1: 1? Будет ли лучше переразметить отображение 1: 1 с другим вспомогательным шаблоном для обратного просмотра, полученного из TypeID? Обратите внимание, что для этого необходимо определить TypeID вне класса Derived.
Действительно ли TypeID должен быть определен внутри класса? Может ли он вывести из базового определения в Base для поддержки существующего использования внутреннего typedef?

Можете ли вы дважды включить? Разбить или выделить ваше определение производного так, чтобы typeid был в определении базового класса, который может быть включен перед шаблоном? Этот DerivedBase может быть объявлен в пространстве имен и содержать ссылку typedef обратно на полный класс Derived, чтобы Base мог найти его для ссылок.

0

На самом деле, я подумал еще немного … это не так уж неприятно:
Вы можете иметь связывающую структуру, даже можно написать как макрос, объявленный непосредственно перед реальным классом.
Структура привязки определяет enum и неполный typedef для реального класса.
Шаблон определен до всего этого, но использует typename для отсрочки зависимости, но он создается экземпляром реального класса и зависит только от структуры привязки.

template <class ThatClassWrapper>
class MyBase
{
protected:
typedef typename ThatClassWrapper::TypeId TypeId;
typedef typename ThatClassWrapper::RealClass ThatClass;
TypeId typeIdValue;
TypeId  GetTypeId() {   return typeIdValue; }
std::vector<ThatClass*> storage;
};

class SomeClass;
namespace TypeIdBinding
{
struct SomeClass
{
enum TypeId
{
hello, world
};
typedef ::SomeClass RealClass;
};
}
class SomeClass: public MyBase<TypeIdBinding::SomeClass>
{
public:
bool CheckValue(TypeId id)
{   return id == typeIdValue;   }
};

Обратите внимание, что реальный класс использует TypeId, как определено в базе шаблонов, и именованные члены не видны напрямую. Вы можете исправить это, получив шаблон Base из структуры связывания (подтвердив, что он таким образом компилируется). хотя на самом деле мне нравится, что в c ++ 11 вы можете экспортировать или ввести по умолчанию просто имя типа enum из другого пространства имен и использовать это имя типа в качестве префикса для членов enum, что помогает избежать загрязнения имен.

0