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.
0 comments
Be the first to start the discussion.