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_
part.
The base_
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_
exists, then derive from nested_
. Otherwise, derive from impl::
.”
template <typename T> struct identity { using type = T; };
Okay, so identity<T>::
is just T
. This is basically a copy of std::
. C++/WinRT supports C++17, but std::
didn’t show up until C++20, so C++/WinRT provides its own copy.
Applying this to base_
simplifies it to “If nested_
exists, then derive from nested_
. Otherwise, derive from root_
.”
So what is nested_
?
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_
. In the base case, nested_
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_
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_
. 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_
, we had previously determined that the definition was “If nested_
exists, then derive from nested_
. Otherwise, derive from impl::
.” Combining this with our discovery that nested_
takes the first interface that is an implements
, we see that the result is that base_
is
- If none of the interfaces is an
implements
, then useroot_
.implements - If exactly one of the interfaces is an
implements
, then use it. (It will provide theroot_
so we don’t have to.)implements - 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::
: 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_
.
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_ |
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.
@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...
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.
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...
@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...
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++.
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?
@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...
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...