Home > Enterprise >  What is the best way to create a 'using' declaration involving members of incomplete types
What is the best way to create a 'using' declaration involving members of incomplete types

Time:06-12

I have a very simple CRTP skeleton structure that contains just one vector and a private accessor in the base class. There is a helper method in the CRTP class to access it.

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

So far so good. Now I want to add a using declaration to CRTPType that will be the type that _v() returns. So ideally, one could do something like the following:

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

The problem is that the type is incomplete, so I cannot use any decltype/declval expressions within CRTPType.

Is there a clean way around this that breaks encapsulation as little as possible? Ideally in C 14, but I would be interested if there are any newer language features that help.

CodePudding user response:

If you don't care so much where and how exactly the using declaration appears, then you can avoid the issue without much changes for example by putting the using declaration into a nested class:

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

The issue in your original code is that using declarations are implicitly instantiated with the class template specialization and the point of implicit instantiation of CRTPType<BaseType> is before the definition of BaseType because the latter uses the former as a base class (which are required to be complete and are therefore implicitly instantiated).

Nested member classes are on the other hand specified not to be implicitly instantiated with the class template specialization and instead only when they are required to be complete. In other words the point of instantiation of the using declaration will now be immediately before the namespace scope declaration which actually, directly or indirectly, uses nested::vtype.


Another alternative is to make the using declaration a template:

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

Member templates can not be implicitly instantiated with the containing class template specialization either. It may be necessary to use the U = T construction and only use U in using = . The reason is that if the type on the right-hand side is not dependent on a template parameter, the compiler is allowed to check whether it can be instantiated immediately after the definition of the template, which is exactly what is not possible and we want to avoid. The program may therefore be ill-formed, no diagnostic required if only T is used. (I am really not 100% sure this applies here, but Clang does actually complain.)


Another possibility is to move the using declaration outside the class, in which case it is obvious that it will not be implicitly instantiated with it:

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

};

Variations using nested classes or namespaces coupled with using declarations in the enclosing namespace can hide the additional name from the outside.


With all of the above you still need to be careful that nothing else in the instantiation of CRTPType<BaseType> or in the definition of BaseType actually indirectly uses vtype. If that happens you may be back to the original problem, potentially even depending on the declaration order of members (although that technically is not standard-conform behavior of the compilers).


In any case, you need to friend the CRTPType<BaseType> in BaseType or mark getV in BaseType as public.

  • Related