创建涉及不完整类型成员的“使用”声明的最佳方法是什么?

Chu*_*huu 9 c++ crtp c++14

我有一个非常简单的 CRTP 骨架结构,其中仅包含一个向量和基类中的一个私有访问器。CRTP 类中有一个辅助方法可以访问它。

#include <vector>

template<typename T>
class CRTPType {

   // Will be used by other member functions.  Note it's private, 
   // so declval/decltype expressions outside of friends or this class 
   // cannot see it
   auto& _v() {
       return static_cast<T *>(this)->getV();
   }
};


class BaseType : public CRTPType<BaseType> {

    friend class CRTPType<BaseType>;

    std::vector<int> v;

    //For different base types this impl will vary
    auto& getV() { return v; }
};
Run Code Online (Sandbox Code Playgroud)

到目前为止,一切都很好。现在我想添加一个using声明,CRTPType该声明将是返回的类型_v()。因此,理想情况下,人们可以执行如下操作:

template<typename T>
class CRTPType {

   //Will be used by other member functions
   auto& _v() {
       return static_cast<T *>(this)->getV();
   }

   using vtype = decltype(std::declval<CRTPType<T>>()._v());
};
Run Code Online (Sandbox Code Playgroud)

问题是类型不完整,所以我不能在 中使用任何decltype/declval表达式CRTPType

有没有一种干净的方法可以尽可能少地破坏封装?理想情况下使用 C++14,但我很感兴趣是否有任何新的语言功能可以提供帮助。

use*_*522 3

如果您不太关心using声明出现的位置和方式,那么您可以避免此问题,而无需进行太多更改,例如通过将using声明放入嵌套类中:

template<typename T>
class CRTPType {

   //Will be used by other member functions
   auto& _v() {
       return static_cast<T *>(this)->getV();
   }

   struct nested {
     using vtype = decltype(std::declval<CRTPType<T>>()._v());
   };
};
Run Code Online (Sandbox Code Playgroud)

原始代码中的问题是,using声明是使用类模板专门化隐式实例化的,并且隐式实例化点CRTPType<BaseType>位于 的定义之前,BaseType因为后者使用前者作为基类(需要完整,因此隐式实例化)实例化)。

另一方面,嵌套成员类被指定使用类模板专门化隐式实例化,而是仅在需要完整时才进行实例化。换句话说,using声明的实例化点现在将紧邻实际直接或间接使用的名称空间范围声明之前nested::vtype


另一种选择是将using声明设为模板:

template<typename T>
class CRTPType {

   //Will be used by other member functions
   auto& _v() {
       return static_cast<T *>(this)->getV();
   }

   template<typename U = T>
   using vtype = decltype(std::declval<CRTPType<U>>()._v());
};
Run Code Online (Sandbox Code Playgroud)

成员模板也不能使用包含类模板特化来隐式实例化。可能需要使用该U = T构造并且仅U在 中使用using = 。原因是,如果右侧的类型不依赖于模板参数,则允许编译器在模板定义后立即检查是否可以实例化,而这正是不可能的,而我们想要的避免。因此,该程序可能格式错误,如果仅T使用,则不需要诊断。(我真的不是 100% 确定这适用于这里,但 Clang 确实抱怨了。)


另一种可能性是将using声明移到类之外,在这种情况下,很明显它不会用它隐式实例化:

template<typename T>
class CRTPType;

template<typename T>
using CRTPType_vtype = decltype(std::declval<CRTPType<T>>()._v());

template<typename T>
class CRTPType {

   //Will be used by other member functions
   auto& _v() {
       return static_cast<T *>(this)->getV();
   }

};
Run Code Online (Sandbox Code Playgroud)

使用嵌套类或名称空间以及using封闭名称空间中的声明的变体可以从外部隐藏附加名称。


有了上述所有内容,您仍然需要小心,在 的实例化CRTPType<BaseType>或定义中没有其他任何内容BaseType实际上间接使用vtype. 如果发生这种情况,您可能会回到原来的问题,甚至可能取决于成员的声明顺序(尽管从技术上讲,这不是编译器的标准行为)。


无论如何,您需要friendinCRTPType<BaseType>或将inBaseType标记为。getVBaseTypepublic