非实例化模板成员的编译时错误,而不是链接时错误

时间:2012-09-27 20:02:49

标签: c++ templates visual-c++ c++03

我有模板类ItemContainer,它实际上是一整套容器的外观,具有不同的功能,如排序,索引,分组等。

使用pimpl习语和显式实例化将实现细节隐藏在cpp.文件中。模板仅使用众所周知的有限实现类实例化,这些实现类定义容器的实际行为。

主模板实现了所有容器支持的常用功能 - 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函数生成很多类。想保留原创设计。

是否有可能将其转换为编译阶段错误而不是链接阶段1?我有一种感觉,静态断言可以某种方式使用。

此代码的目标编译器是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

4 个答案:

答案 0 :(得分:4)

如果在编译时知道某个函数不会被实现,那么该函数不应该首先被声明。否则,这是一个编程错误。

话虽如此,你必须避免声明这样一个函数,或者声明它只有在它被实现时才能生效。这可以通过static_assert或SFINAE来实现。 例如

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

};

答案 1 :(得分:3)

考虑这个例子:

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;

答案 2 :(得分:2)

尽管建议使用SFINAE的答案很好,但我继续搜索符合我原始设计的解决方案。最后我发现了它。

关键的想法是对特定的函数成员使用 specialization 而不是显式实例化。

做了什么:

  1. 为主模板添加了特定功能虚拟实现。仅包含静态断言的实现被放置在头文件中,但没有内联到类定义中。
  2. .cpp文件中删除了特定功能显式实例化。
  3. 特定功能专业化声明已添加到头文件中。
  4. 源代码:

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

    对于使用C ++ 11的GCC static_assert使用需要一些技巧来启用延迟模板函数实例化(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!
    }
    

答案 3 :(得分:1)

这个怎么样?

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文件中编写实现。