Compile-time error for non-instantiated template members instead of link-time error

▼魔方 西西 提交于 2019-12-07 15:42:43

问题


I have template class ItemContainer that actually is facade for a whole family of containers with different capabilities like sorting, indexing, grouping etc.

Implementation details are hidden in cpp. file using pimpl idiom and explicit instantiation. Template is instantiated only with well-known limited set of implementation classes that define the actual behavior of container.

Main template implements common functions supported by all containers - IsEmpty(), GetCount(), Clear() etc.

Each specific container specializes some functions that are supported only by it, e.g. Sort() for sorted container, operator[Key&] for key indexed container etc.

The reason for such design is that class is replacement for several legacy hand-made bicycle containers written by some prehistorics in early 90th. Idea is to replace old rotting implemenation with modern STL&Boost containers keeping old interface untouched as much as possible.

The problem

Such design leads to unpleasant situation when user tries to call unsupported function from some specialization. It compiles OK, but produces error on linking stage (symbol not defined). Not very user friendly behavior.

Example:

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

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

Of course, it could be completely avoided by using inheritance instead of specialization but I don't like to produce a lot of classes with 1-3 functions. Would like to keep original design.

Is there possibility to turn it into compile stage error instead of link stage one? I have a feeling that static assert could be used somehow.

Target compiler for this code is VS2008, so practical solution must be C++03 compatible and could use MS specific features. But portable C++11 solutions also are welcome.

Source Code:

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

回答1:


If it is known at compile time that a certain function will not be implemented, then that function shouldn't have been declared in the first place. Otherwise, this is a programming error.

Having said that, you must avoid declaring such a function, or declare it such that the declaration only works if it will be implemented. That can be achieved either by a static_assert or by SFINAE. For example

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

};



回答2:


Consider this example:

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

Only during linking phase it could be detected an error that A::bar() is not defined and this has nothing to do with templates.

You shall define separate interfaces for your different containers and use them for your implementations. Just one of the possibilities below:

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:


Despite the good answers suggesting to use SFINAE, I continued to search solution that will meet my original design. And finally I found it.

The key idea is to use specialization for specific function members instead of explicit instantiation.

What was done:

  1. Added specific functions dummy implementation for main template. Implementations containing static asserts only were placed in header file but not inlined into class definition.
  2. Specific functions explicit instantiations were removed from .cpp file.
  3. Specific functions specialization declarations were added to header file.

Source Code:

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

This way it works at least for VS2008.

For GCC with C++11 static_assert usage requires some trick to enable lazy template function instatiation (compiled sample):

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!
}



回答4:


What about this ?

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;
}; 

I find that using inheritance is the most clean way to achieve what you would like to do (which is 'adding interfaces to a class'.) Then we have the following:

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
}

Of course, you still are able to write the implementation in .cpp files.



来源:https://stackoverflow.com/questions/12629191/compile-time-error-for-non-instantiated-template-members-instead-of-link-time-er

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!