February 21st, 2025

C++/WinRT implementation inheritance: Notes on winrt::implements, part 3

In C++/WinRT, the implements template type starts like this:

template <typename D, typename... I>
struct implements :
    impl::producers<D, I...>,
    impl::base_implements<D, I...>::type
{

The producers template type generates the vtables for the COM interfaces. But that’s not what we’re looking at today. Today we’re going to look at the base_implements part.

The base_implements type is defined as follows:

template <typename D, typename Dummy = std::void_t<>, typename... I>
struct base_implements_impl
    : impl::identity<root_implements<D, I...>> {};

template <typename D, typename... I>
struct base_implements_impl<D, std::void_t<typename nested_implements<I...>::type>, I...>
    : nested_implements<I...> {};

template <typename D, typename... I>
using base_implements = base_implements_impl<D, void, I...>;

We learned from last time that this uses SFINAE: to implement an “if then else” pattern. In this case, it’s saying “If nested_implements<I...>::type exists, then derive from nested_implements<I...>. Otherwise, derive from impl::identity<root_implements<D, I...>>.”

template <typename T>
struct identity
{
    using type = T;
};

Okay, so identity<T>::type is just T. This is basically a copy of std::type_identity. C++/WinRT supports C++17, but std::type_identity didn’t show up until C++20, so C++/WinRT provides its own copy.

Applying this to base_implements_impl simplifies it to “If nested_implements<I...>::type exists, then derive from nested_implements<I...>. Otherwise, derive from root_implements<D, I...>.”

So what is nested_implements?

template <typename...>
struct nested_implements
{};

template <typename First, typename... Rest>
struct nested_implements<First, Rest...>
    : std::conditional_t<is_implements_v<First>,
    impl::identity<First>, nested_implements<Rest...>>
{
    static_assert(
        !is_implements_v<First> ||
        !std::disjunction_v<is_implements<Rest>...>,
        "Duplicate nested implements found");
};

This is a recursively-defined nested_implements. In the base case, nested_implements<> is an empty class. Otherwise, we peel off the first template parameter and see if it derives from implements. If so, then we use it. Otherwise, we recurse on the remaining parameters.

So nested_implements searches through the template parameters and takes the first one that derives from implements. Otherwise, it’s an empty class.

But wait, there’s extra work done in the static_assert. First, let’s translate it from C++ template-ese to pseudo-code. The std::disjunction takes the logical OR of its arguments, so the second part expands to !(is_implements_v<Rest1> || is_implements_v<Rest2> || ...), which says “None of the Rest is an implements.”

Now combine this with the first part, and we get “Either First is not an implements, or none of the Rest is an implements.” If you transform this to an implication relation, you get “If First is an implements, then none of the Rest is an implements.”

During the recursion, First progresses through all of the interface arguments, so the assertion verifies that at most one of the interface arguments supports implements.

Okay, so unwinding back to base_implements, we had previously determined that the definition was “If nested_implements<I...>::type exists, then derive from nested_implements<I...>. Otherwise, derive from impl::identity<root_implements<D, I...>>.” Combining this with our discovery that nested_implements takes the first interface that is an implements, we see that the result is that base_implements is

  • If none of the interfaces is an implements, then use root_implements.
  • If exactly one of the interfaces is an implements, then use it. (It will provide the root_implements so we don’t have to.)
  • If more than one of the interfaces is an implements, then raise a compile-time error.

From all this, you can figure out the legal inheritance structures for winrt::implements: The implements must form a single chain of inheritance, possibly passing through other non-implements classes along the way. You cannot inherit (directly or indirectly) from multiple implements. The innermost implements provides the root_implements.

    A       A : implements<A, I1, B, I2>, X1
         
    implements   X1      
       
produce<A, I1>   B   produce<A, I2>   B : implements<B, C, I3>
           
    implements      
         
    C   produce<B, I3>   C : D, X2
         
    D   X2   D : implements<D, I4, I5>
       
root_implements   produce<D, I4>   produce<D, I5>  

Next time, we’ll look at how you can employ base classes and inheritance in your Windows Runtime implementation classes while still adhering to these restrictions.

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.

8 comments

  • Dmitry 18 hours ago · Edited

    @Igor Levicki

    Never say ”never” or ”nobody”. You’ll feel the value of fat-free software sitting on a tree in the middle of a forest with very slow mobile internet connection trying to download an app that claims to help with your problem — a hungry bear climbing up to you.

    While funny, software size is a thing to consider. Ease of updates, cache-friendliness, and all that apart from the root causes like overuse of bloated third-party junk instead of hiring a programmer.

    Still I do agree with the main point. C++ is the best source code obfuscation tool I’ve ever seen, and it...

    Read more
    • Igor Levicki 10 hours ago

      Updates nowadays are automated and often even forced so the users have no choice aside from not using your application if they are unhappy with its size.

      My main point was that it shouldn’t be necessary to have 5 blog articles from an expert C++ programmer just to explain how to correctly inherit from a WinRT interface.

      • Me Gusta · Edited

        This isn't a series of posts about "how to correctly inherit from a WinRT interface". This is a series of posts about what goes on under the surface. Raymond is explaining the TMP that is going on to get C++/WinRT to do what it does.

        Also, this is also not really a series for beginners or those who just want to implement simple components. Most cases with C++/WinRT will see you using the executable, either directly or indirectly, generate a source and header file for you and you just use that. Figuring out how to use the implements template is an...

        Read more
  • Igor Levicki 2 days ago

    @Me Gusta

    The thing is, nobody cares about application sizes today -- I am not saying that's good or that's how it should be, just stating a fact. For example, Microsoft Teams folder is 266 MB, who cares whether .exe is 266 MB or 21.80 MB with the rest being DLLs and resources? Nobody, that's who -- end user only sees the folder size if even that.

    As for your statement "Why not? Conditional compilation exists." it is totally absurd on its head. Major software vendors just aren't using WinRT. Should they? Perhaps, but they won't because they want to have a...

    Read more
  • Igor Levicki 3 days ago · Edited

    So if I am getting this right, you guys are effectively using modern C++17(+) templating features to break the C++ language OOP model of multiple inheritance, polymorphism, and direct class composition?

    I guess nobody thought of, you know, using C# instead of trying to emulate its inheritance model in C++.

    • Me Gusta

      C++/WinRT is a native C++ projection for the Windows Runtime, for use in C++ applications. Are you suggesting that the design should have forced a dependency on .NET and bloated a C++ application’s install size?

      • Me Gusta 3 days ago

        @Igor Levicki

        "No, C++/WinRT is C#-ized C++, nothing else."

        Nope, there is so much more going on here. TMP, constexpr/consteval allows much more than what C# allows. One of the biggest things is that almost everything is zero runtime overhead. Almost everything will optimise away and what is left is close to just the ABI calls. Then there is the fact that it is almost zero space overhead, so all of those templates will just evaporate into nothing if not used. C# Generics can't do this, and in fact, the C# WinRT projection is over 30MiB. Even IL trimming and AoT cannot...

        Read more
      • Igor Levicki 3 days ago

        No, C++/WinRT is C#-ized C++, nothing else.

        Besides, biggest / most relevant desktop apps aren't using it and can't use it anyway if they want to be portable and nowadays most do -- it's either Chrome Electron Framework, Microsoft Edge WebView2, or Qt Framework for UI so when you are not using XAML and not restricted to UWP since you aren't targetting Microsoft Store what's exactly the point of using C++/WinRT?

        As for your claim that .Net runtime would bloat install size? .Net runtime could have been bundled into the OS just like they did with .Net Framework 4.8(.1). Most C++ developers...

        Read more