У меня есть класс шаблона ItemContainer
, который на самом деле является фасадом для целого семейства контейнеров с различными возможностями, такими как сортировка, индексация, группировка и т. д.
Детали реализации скрыты в файле cpp.
с использованием идиомы pimpl и явного создания экземпляров. Шаблон создается только с хорошо известным ограниченным набором классов реализации, которые определяют фактическое поведение контейнера.
Основной шаблон реализует общие функции, поддерживаемые всеми контейнерами — IsEmpty()
, GetCount()
, Clear()
и т. д.
Каждый конкретный контейнер специализируется на некоторых функциях, которые поддерживаются только им, например. Sort()
для отсортированного контейнера, operator[Key&]
для индексированного по ключу контейнера и т. д.
Причина такого дизайна в том, что класс является заменой нескольким устаревшим велосипедным контейнерам ручной работы, написанным какими-то доисториками в начале 90-х. Идея состоит в том, чтобы заменить старую гниющую реализацию современными контейнерами STL&Boost, максимально сохранив старый интерфейс нетронутым.
Проблема
Такой дизайн приводит к неприятной ситуации, когда пользователь пытается вызвать неподдерживаемую функцию из какой-то специализации. Компилируется нормально, но на этапе линковки выдает ошибку (символ не определен). Не очень удобное поведение.
Пример:
SortedItemContainer sc;
sc.IsEmpty(); // OK
sc.Sort(); // OK
IndexedItemContainer ic;
ic.IsEmpty(); // OK
ic.Sort(); // Compiles OK, but linking fails
Конечно, этого можно было бы полностью избежать, используя наследование вместо специализации, но я не люблю создавать много классов с 1-3 функциями. Хотелось бы сохранить оригинальный дизайн.
Есть ли возможность превратить его в ошибку этапа компиляции вместо первого этапа ссылки? У меня есть ощущение, что статическое утверждение можно как-то использовать.
Целевым компилятором для этого кода является VS2008, поэтому практическое решение должно быть совместимо с C++03 и может использовать специфические функции MS. Но портативные решения C++11 также приветствуются.
Исходный код:
// ItemContainer.h
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <class Impl> class ItemContainer
{
public:
// Common functions supported by all specializations
void Clear();
bool IsEmpty() const;
...
// Functions supported by sequenced specializations only
ItemPtr operator[](size_t i_index) const;
...
// Functions supported by indexed specializations only
ItemPtr operator[](const PrimaryKey& i_key) const;
...
// Functions supported by sorted specializations only
void Sort();
...
private:
boost::scoped_ptr<Impl> m_data; ///< Internal container implementation
}; // class ItemContainer
// Forward declarations for pimpl classes, they are defined in ItemContainer.cpp
struct SequencedImpl;
struct IndexedImpl;
struct SortedImpl;
// Typedefs for specializations that are explicitly instantiated
typedef ItemContainer<SequencedImpl> SequencedItemContainer;
typedef ItemContainer<IndexedImpl> IndexedItemContainer;
typedef ItemContainer<SortedImpl> SortedItemContainer;
// ItemContainer.cpp
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation classes definition, skipped as non-relevant
struct SequencedImpl { ... };
struct IndexedImpl { ... };
struct SortedImpl { ... };
// Explicit instantiation of members of SequencedItemContainer
template void SequencedItemContainer::Clear(); // Common
template bool SequencedItemContainer::IsEmpty() const; // Common
template ItemPtr SequencedItemContainer::operator[](size_t i_index) const; // Specific
// Explicit instantiation of members of IndexedItemContainer
template void IndexedItemContainer::Clear(); // Common
template bool IndexedItemContainer::IsEmpty() const; // Common
template ItemPtr IndexedItemContainer::operator[](const PrimaryKey& i_key) const; // Specific
// Explicit instantiation of members of SortedItemContainer
template void SortedItemContainer::Clear(); // Common
template bool SortedItemContainer::IsEmpty() const; // Common
template void SortedItemContainer::Sort(); // Specific
// Common functions are implemented as main template members
template <class Impl> bool ItemContainer<Impl>::IsEmpty() const
{
return m_data->empty(); // Just sample
}
// Specialized functions are implemented as specialized members (partial specialization)
template <> void SortedItemContaner::Sort()
{
std::sort(m_data.begin(), m_data.end(), SortFunctor()); // Just sample
}
...
// etc