Ошибка времени компиляции для неинстанцированных элементов шаблона вместо ошибки времени компоновки

У меня есть шаблон класса ItemContainer на самом деле это фасад для целого семейства контейнеров с различными возможностями, такими как сортировка, индексация, группировка и т. д.

Детали реализации скрыты в cpp. файл с использованием идиомы pimpl и явной реализации. Шаблон создается только с хорошо известным ограниченным набором классов реализации, которые определяют реальное поведение контейнера.

Основной шаблон реализует общие функции, поддерживаемые всеми контейнерами — IsEmpty(), GetCount(), Clear() и т.п.

Каждый конкретный контейнер специализируется на некоторых функциях, которые поддерживаются только им, например, Sort() для отсортированного контейнера, operator[Key&] для индексированного контейнера и т. д.

Причиной такого дизайна является то, что этот класс является заменой нескольких устаревших велосипедных контейнеров ручной работы, написанных доисторическими авторами в начале 90-х годов. Идея состоит в том, чтобы заменить старые гниющие устройства на современные STL.&Увеличьте контейнеры, сохраняя старый интерфейс в максимально возможной степени.

Эта проблема

Такой дизайн приводит к неприятной ситуации, когда пользователь пытается вызвать неподдерживаемую функцию из какой-то специализации. Он компилируется нормально, но выдает ошибку на этапе компоновки (символ не определен).
Не очень удобное поведение.

Пример:

 SortedItemContainer sc;
sc.IsEmpty(); // OK
sc.Sort(); // OK

IndexedItemContainer ic;
ic.IsEmpty(); // OK
ic.Sort(); // Compiles OK, but linking fails

Конечно, этого можно полностью избежать, используя наследование вместо специализации, но я не люблю создавать много классов с 1-3 функциями. Хотелось бы сохранить оригинальный дизайн.

Есть ли возможность превратить его в ошибку этапа компиляции вместо первого этапа компоновки? У меня такое ощущение, что static assert можно каким-то образом использовать.

Целевым компилятором для этого кода является 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

3

Решение

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

Сказав это, вы должны избегать объявления такой функции или объявлять ее так, чтобы объявление работало только в том случае, если оно будет реализовано. Это может быть достигнуто либо static_assert или СФИНА.
Например

template<class Container>   // you need one instantination per container supported
struct container_traits
{
static const bool has_sort;  // define appropriately in instantinations
/* etc */
};

template<class container>
class ContainerWrapper {

unique_ptr<container> _m_container;

template<bool sorting> typename std::enable_if< sorting>::type
_m_sort()
{
_m_container->sort();
}

template<bool sorting> typename std::enable_if<!sorting>::type
_m_sort()
{
static_assert(0,"sort not supported");
}

public

void sort()
{
_m_sort<container_traits<container>::has_sort>();
}

/* etc */

};
4

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

Рассмотрим этот пример:

class A {
public:
void foo() {}
void bar();
};

Только на этапе связывания может быть обнаружена ошибка, A::bar() не определяется, и это не имеет ничего общего с шаблонами.

Вы должны определить отдельные интерфейсы для ваших различных контейнеров и использовать их для своих реализаций. Просто одна из возможностей ниже:

template <class Impl>
class ItemContainerImpl
{
public:
ItemContainerImpl();
protected:
boost::scoped_ptr<Impl> m_data; ///< Internal container implementation
};

// No operations
template <class Impl>
class Empty : protected virtual ItemContainerImpl<Impl> {};

template <class Impl, template <class> class Access, template <class> class Extra = Empty>
class ItemContainer : public Extra<Impl>, public Access<Impl>
{
public:

// Common functions supported by all specializations
void Clear();
bool IsEmpty() const;
...
};

template <class Impl>
class SequencedSpecialization : protected virtual ItemContainerImpl<Impl> {
public:
// Functions supported by sequenced specializations only
ItemPtr operator[](size_t i_index) const;
...
};template <class Impl>
class IndexedSpecialization : protected virtual ItemContainerImpl<Impl> {
public:
// Functions supported by indexed specializations only
ItemPtr operator[](const PrimaryKey& i_key) const;
...
};

template <class Impl>
class Sorted : protected virtual ItemContainerImpl<Impl> {
public:
// Functions supported by sorted specializations only
void Sort();
...
};

// Typedefs for specializations that are explicitly instantiated
typedef ItemContainer<SequencedImpl, SequencedSpecialization> SequencedItemContainer;
typedef ItemContainer<IndexedImpl, IndexedSpecialization> IndexedItemContainer;
typedef ItemContainer<SortedImpl, IndexedSpecialization, Sorted> SortedItemContainer;
3

Несмотря на хорошие ответы, предлагающие использовать SFINAE, я продолжил поиск решения, соответствующего моему оригинальному дизайну. И наконец я нашел это.

Основная идея заключается в использовании специализация для конкретных членов функции вместо явной реализации.

Что было сделано:

  1. Добавлены конкретные функции фиктивной реализации для основного шаблона. Реализации, содержащие только статические утверждения, были помещены в заголовочный файл, но не включены в определение класса.
  2. Конкретные функции явных экземпляров были удалены из .cpp файл.
  3. Определенные объявления специализации функций были добавлены в заголовочный файл.

Исходный код:

// ItemContainer.h
//////////////////////////////////////////////////////////////////////////////
template <class Impl> class ItemContainer
{
public:

// Common functions supported by all specializations
void Clear();
bool IsEmpty() const;
...

// Functions supported by sorted specializations only
void Sort();
...

private:

boost::scoped_ptr<Impl> m_data; ///< Internal container implementation

}; // class ItemContainer

// Dummy implementation of specialized function for main template
template <class Impl> void ItemContainer<Impl>::Sort()
{
// This function is unsupported in calling specialization
BOOST_STATIC_ASSERT(false);
}

// Forward declarations for pimpl classes,
// they are defined in ItemContainer.cpp
struct SortedImpl;

// Typedefs for specializations that are explicitly instantiated
typedef ItemContainer<SortedImpl> SortedItemContainer;

// Forward declaration of specialized function member
template<> void CSortedOrderContainer::Sort();

// ItemContainer.cpp
//////////////////////////////////////////////////////////////////////////////

// Implementation classes definition, skipped as non-relevant
struct SortedImpl { ... };

// Explicit instantiation of common members of SortedItemContainer
template  void SortedItemContainer::Clear();
template  bool SortedItemContainer::IsEmpty() const;

// 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

Таким образом, это работает по крайней мере для VS2008.

Для GCC с C ++ 11 static_assert использование требует некоторого трюка для включения отложенной функции шаблона (скомпилированный образец):

template <class T> struct X
{
void f();
};

template<class T> void X<T>::f()
{
// Could not just use static_assert(false) - it will not compile.
// sizeof(T) == 0 is calculated only on template instantiation and
// doesn't produce immediate compilation error
static_assert(sizeof(T) == 0, "Not implemented");
}

template<> void X<int>::f()
{
std::cout << "X<int>::f() called" << std::endl;
}

int main()
{
X<int> a;
a.f(); // Compiles OK

X<double> b;
b.f(); // Compilation error - Not implemented!
}
2

Как насчет этого ?

template <class T, class supported_types> struct vec_enabler :
boost::mpl::contains<supported_types, T> {};

// adding Sort interface
template <class T, class enabler, class Enable = void>
struct sort_cap{};

template <class T, class enabler>
struct sort_cap<T, enabler,
typename boost::enable_if< typename enabler::type >::type>
{
void Sort();
};

// adding operator[]
template <class T, class U, class R, class enabler, class Enable = void>
struct index_cap{};

template <class T, class primary_key, class ret, class enabler>
struct index_cap<T, primary_key, ret, enabler,
typename boost::enable_if< typename enabler::type >::type>
{
ret operator[](primary_key i_index) const;
};template <class Impl>
class ItemContainer :
public sort_cap<Impl,
vec_enabler<Impl, boost::mpl::vector<A, B> > >, // sort for classes A or B
public index_cap<Impl, size_t, ItemPtr,
vec_enabler<Impl, boost::mpl::vector<C> > >, // index for class C
public index_cap<Impl, primaryKey, ItemPtr,
vec_enabler<Impl, boost::mpl::vector<B> > > // index for class B
{
public:
void Clear();
bool IsEmpty() const;
};

Я считаю, что использование наследования является наиболее чистым способом достижения того, что вы хотели бы сделать (то есть «добавление интерфейсов в класс»). Тогда у нас есть следующее:

int main(){
ItemContainer<A> cA;
cA.Sort();

//ItemPtr p = cA[0]; // compile time error

ItemContainer<C> cC;
//cC.Sort(); // compile time error
ItemPtr p = cC[0];
//ItemPtr pp= cC[primaryKey()]; // compile time error
}

Конечно, вы все еще можете написать реализацию в .cpp файлах.

1
По вопросам рекламы [email protected]