March 12th, 2024

How well does _com_ptr_t support class template argument deduction (CTAD)?

We saw earlier that the _com_ptr_t class template doesn’t work well with class template argument deduction (CTAD).

Template argument deduction fails on this constructor:

template<typename _IIID> class _com_ptr_t {
public:
    using ThisIIID = _IIID;
    using Interface = typename _IIID::Interface;

    _com_ptr_t(Interface* pInterface); // this one

    ⟦ other stuff ⟧
};

The problem is that the compiler can’t figure out what _IIID to use that will produce the desired Interface. After all, there are an infinite number of such classes.

struct SomeRandomClass
{
    using Interface = ::IWidget;
};

struct AnotherRandomClass
{
    using Interface = ::IWidget;
};

Both of these random classes are viable candidates for the template type argument _IIID. Clearly there is no reason for the compiler to choose one over the other. The general rule is that the compiler will try to deduce the template arguments from the parameter list, but this requires that the parameter list involves the template arguments at all!

The library intends the _IIID to be a specialization of a helper type named _com_IIID, where IIID presumably stands for Interface and Interface identifier.

template<typename _Interface, const IID* _IID /*= &__uuidof(_Interface)*/>
class _com_IIID {
public:
    typedef _Interface Interface;

    static const IID& GetIID() noexcept
    {
        return *_IID;
    }

    ⟦ other stuff ⟧
};

If the library had the ability to see into the C++17 future, it could have provided a deduction guide:

template<typename T> _com_ptr_t(T*)
    -> _com_ptr_t<_com_IIID<T, &__uuidof(T)>>;

This tells the compiler that if it sees a _com_ptr_t(T*), it should construct a _com_ptr_t<_com_IIID<T, &__uuidof(T)>>.

As a rule of thumb, you shouldn’t create deduction guides for types defined in someone else’s library, because a future version of the library might add a deduction guide, and now you have a conflict between your deduction guide and the library’s. Deduction guides can be thought of as constructor metadata, and the class controls its own constructors.

In practice, this lack of CTAD support is not an issue because you don’t use _com_ptr_t directly; instead, you use the _COM_SMARTPTR_TYPEDEF macro to define a custom type name for your specialized smart pointer, and then you use that custom type name from then on.

_COM_SMARTPTR_TYPEDEF(IWidget, __uuidof(IWidget));

IWidget* p;
auto smart = IWidgetPtr(p);

So really, this was much ado about nothing.

But what if you really want to avoid typing out the interface name when using smart pointers based on _com_ptr_t? We’ll find the answer when we look at MFC IPTR/CIP next time.

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

0 comments

Discussion are closed.