Search code examples
comc++17variadic-templatessfinaefold-expression

Limit allowed COM Interfaces by using template function with limited variadic parameters (std::is_same)


I have wrapper classes contaning COM pointers (or smart pointers) to different interfaces.

INTEND: Some COM classes can be obtained from various other COM interfaces, and I want to make a template constructor with variadic types, which would allow passing arguments only of appropriate types.

something like:

template <class T, class = typename 
   std::enable_if<std::is_base_of<IUnknown, T>::value>::type, class ... Types>
   class WithCOMptrbase
{
   protected:
   T* ptr_;

   public: 

   //construct smart pointer by copy
   WithCOMptrbase(T* ptr, bool AddRef = false)
     : ptr_(ptr)
   {
      if (AddRef) ptr_->AddRef();
   }

   /*construct a smart pointer by querying an interface from an argument of 
   a type which is the same as one of the variadics*/
   template <class TypeOther, class = typename
      std::enable_if<syd::is_same<Types... , TypeOther>::value... || 
      ...>::type> /*there needs to be a proper version*/
      WithCOMptrbase(TypeOther* ptr)
        : ptr_(cQueryInterface<T>(ptr))
      {}

//other methods
};

helper function:

template <class U, class = typename 
   std::enable_if<std::is_base_of<IUnknown, U>::value>::type>
   T* cQueryInterface<T>(U *ptr)
{
   T* out;
   HRESULT hr = ptr->QueryInterface(__uuidof(T), (void**)&out);
   if (!SUCCEEED(hr)) throw _com_error(hr);
   return out;
}

Therefore, I will define my wrapper class

class WrapperClass : protected WithCOMptrbase<IThis, IInterface1, IInterface2, IInterface3>
{
   //methods
};

So far I have found this thread: How to make a variadic is_same? but it is only about structs, not functions. My goal is to limit the possibility of passing inapproprtiate Interface pointer, hence not to deal with wrong interface errors at runtime.

UPDATE: Since Composition is preferable over inheritance, I've done some rethinking and decided to use a template function rather than a template class. So far I've managed to combine given answers and came up with this:

template <bool S, class Out, class Other, typename
std::enable_if<S>::type* = nullptr>
//copy-construct Smart Pointer for same Interfaces
WComPtr<Out> WFilterSame(const WComPtr<Other>& pOther)
{
    return WComPtr<Out>(pOther);
}

template <bool S, class Out, class Other, typename
    std::enable_if<!S>::type* = nullptr>
//Query Interface if differ
WComPtr<Out> WFilterSame(const WComPtr<Other>& pOther)
{
    return pOther.QueryInterface<Out>();
}

template <class Out, class ... Permitted, class Other>
WComPtr<Out> WFilterComInterfPtr(const WComPtr<Other>& pOther)
{

    static_assert(std::is_same<Out, Other>::value ||
        (std::is_same<Permitted, Other>::value || ...),
        "Interface is not supported.");

    return WFilterSame<std::is_same<Out, Other>::value, Out>(pOther);
}

Now I can define a constructor of my COM wrapper class:

class WComClass
{
   private:
   WComPtr<Interface> pComPtr_; //My Smart COM pointer

   template <class Other>
   WComPtr<Interface> WFilter(const WComPtr<Other>& pOther)
   {
      return WFilterComInterfPtr<Interface, IAllowed1, IAllowed2>(pOther);
   }

   public:
   template <class Other>
   WComClass(const WComPtr<Other>& pOther)
      : pComPtr_(WFilter(pOther))
   {}

   //methods
};

So far it behaved as intended (WFilterComInterfPtr), I don't expect it to fail in the wrapper class costructor.


Solution

  • Try with

       template <class TypeOther, class =
          std::enable_if_t<(std::is_same_v<Types, TypeOther> || ...)>>        
          WithCOMptrbase(TypeOther* ptr)
            : ptr_(cQueryInterface<T>(ptr))
          {}
    

    I mean... you're using three ellipsis instead of one (remove the ellipsis after ::value and the one after Types) and you need an additional couple of parentheses.

    Off topic: are you sure that works

    template <class T, class ... Types, class = typename 
       std::enable_if<std::is_base_of<IUnknown, T>::value>::type>
       class WithCOMptrbase
    

    ?

    SFINAE through a default type after a variadic list?