2

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.

max66
  • 65,235
  • 10
  • 71
  • 111
Sergey Kolesnik
  • 3,009
  • 1
  • 8
  • 28
  • Unless you restrict what version of C++, we'll assume [tag:C++17] at this point. Especially because it makes it easier to solve. :) – Yakk - Adam Nevraumont Nov 15 '18 at 18:23
  • Second, what goes wrong with your solution? A syntax error in `WithCOMptrbase`? – Yakk - Adam Nevraumont Nov 15 '18 at 18:25
  • Do you want the stored TypeOther to support *all* of the interfaces, or *one* of the interfaces? One seems strange. Ah, you want only poitners of `T` or` Types...` to be permited, and then you want to always store it as a `T`? Sort of like a variant but no way to go back to the type stored. – Yakk - Adam Nevraumont Nov 15 '18 at 18:29
  • @Yakk-AdamNevraumont I want to support All of the provided interfaces. – Sergey Kolesnik Nov 15 '18 at 18:35
  • You could replace `class = typename std::enable_if::value>::type` by `static_assert(std::is_base_of_v);`. If you don't have any fallback (and in the case of a primary template you don't), then this substitution failure will eventually lead to a hard error due to no alternative to pick – Piotr Skotnicki Nov 15 '18 at 18:36
  • @SergeyKolesnik That is shockingly uninformative. "I want to support All" doesn't tell me if the passed in type `TypeOther` must support each and every one of the interfaces, or you want to support if any of that list is passed in, or something else; this is because "support" is ambiguous. – Yakk - Adam Nevraumont Nov 15 '18 at 18:54
  • 2
    @Yakk-AdamNevraumont, the Idea is to allow passing only the provided Interfaces, so in my case I would be able to construct WithCOMptrbase only when "IThis, IInterface1, IInterface2, IInterface3" types are passed as arguments. For now I successfully compile with those allowed tyes and get compile error for other (not allowed) interfaces. – Sergey Kolesnik Nov 15 '18 at 19:01

2 Answers2

4

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?

max66
  • 65,235
  • 10
  • 71
  • 111
  • 2
    `(std::is_same_v || ...)` – Piotr Skotnicki Nov 15 '18 at 18:28
  • 1
    Replacing `class = std::enable_if_t<(syd::is_same_v || ...)>` with `std::enable_if_t<(syd::is_same_v || ...), bool> =true` permits multiple ctor overloads. Also you have too many `...`, try `std::enable_if_t<(syd::is_same_v || ...), bool> =true` – Yakk - Adam Nevraumont Nov 15 '18 at 18:28
  • @PiotrSkotnicki - Ops... not seen the one after `Types`; thanks. – max66 Nov 15 '18 at 18:33
  • @Yakk-AdamNevraumont - thanks; yes: not seen the ellipsis after `Types`; nice trick the one of the `bool` type; but not useful in this case (with only a constructor), if I understand correctly. – max66 Nov 15 '18 at 18:39
  • @max66 Works fine, though I have some problems with me wrapper class, but I guess it is another story. And yes, variadics need to be placed at the end. Thank you. – Sergey Kolesnik Nov 15 '18 at 18:48
  • 1
    @max66 Imagine they add a 2nd constructor which takes one template `T*` and a SFINAE "test" clause. With this design they conflict with annoying error messages; with the `, bool> = true` design they don't I'm just suggesting a better default SFINAE "cargo cult" trick to always use to avoid a potential issue that may or may not apply in any given case. – Yakk - Adam Nevraumont Nov 15 '18 at 18:53
  • @max66 I don't know what have changed, but since there are "'. It worked and now it's broken( – Sergey Kolesnik Nov 15 '18 at 20:02
  • @Yakk-AdamNevraumont - I see the point (usually I use `std::enable_if_t< some test> * = nullptr` (because I'm lazy and use the default value for second template parameter of `std::enable_if`)) but I had never thought of use this technique as default; also when there is only one constructor. But, in effect... today I write a single constructor and I don't think to add another one... the next year I write another constructor... with my system I have a clash... Yes: I think is a good idea. – max66 Nov 15 '18 at 20:31
  • @SergeyKolesnik - Sorry but I can't imagine what's wrong now; I can just say that `std::is_same` is wrong (if `1 != sizeof...(Types)`, obviously). You should try preparing a full (and minimal) example. But I suppose isn't easy. – max66 Nov 15 '18 at 20:37
  • @max66, currently I have a working code which is: template || ...)>> WithCOMptrbase(WComPtr ptr) : ptr_(ptr.QueryInterface()) {} – Sergey Kolesnik Nov 15 '18 at 20:41
  • 1
    @max66 Last I checked, `void*` wasn't a valid type of a non-type template parameter, but I could imagine that had changed. – Yakk - Adam Nevraumont Nov 15 '18 at 20:43
  • @SergeyKolesnik - do you mean that is working `std::is_same_v`? But what is `Types...`? Anyway... you're defining `TypeOther` using `TypeOther`. And the compiler doesn't complain? – max66 Nov 15 '18 at 21:52
  • @Yakk-AdamNevraumont - D'Oh! I didn't know at all that `void *` isn't a valid type for a non-type template parameter. And I've used it zillion of times! You have convinced me: `std::enable_if_t = true`. Thanks. – max66 Nov 15 '18 at 22:12
  • @max I know it wasn't once, it may be now. – Yakk - Adam Nevraumont Nov 16 '18 at 00:25
  • @max66, yes, it works, in contrast to not having Types expanded. Types... are the additional types to be accepted as a constructor arguments, and we compare TypeOther with each from expansion. I don't have a desktop with the internet connection now, so I ll try to update my question later when come home. – Sergey Kolesnik Nov 16 '18 at 03:30
1

How about CRTP to avoid some template:

template <typename Base, typename T>
class Impl
{
public:
    Impl() = default;
    explicit Impl(T*) { static_cast<Base*>(this)->ptr_ = cQueryInterface<T>(ptr); }
};

template <class T, class ... Ts>
class WithCOMptrbase : private Impl<WithCOMptrbase<T, Ts...>, Ts>...
{
    static_assert(std::is_base_of<IUnknown, T>::value);
    static_assert((std::is_base_of<IUnknown, Ts>::value && ...));

    template <typename, typename> friend struct Impl; // We don't have variadic friend :/
protected:
    T* ptr_ = nullptr;


public:
    using Impl<WithCOMptrbase, Ts>::Impl...;

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

    //other methods
};
Jarod42
  • 203,559
  • 14
  • 181
  • 302