Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qquasivirtual_impl.h
Go to the documentation of this file.
1// Copyright (C) 2025 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#ifndef QQUASIVIRTUAL_IMPL_H
6#define QQUASIVIRTUAL_IMPL_H
7
8#if 0
9#pragma qt_sync_skip_header_check
10#pragma qt_sync_stop_processing
11#endif
12
13#include <QtCore/qscopeguard.h>
14#include <QtCore/qassert.h>
15#include <QtCore/qtclasshelpermacros.h>
16
17#include <algorithm>
18#include <type_traits>
19#include <tuple>
20
21#ifndef Q_QDOC
22
23QT_BEGIN_NAMESPACE
24
25namespace QtPrivate {
26
27template <typename Applier, size_t ...Is>
28void applyIndexSwitch(size_t index, Applier&& applier, std::index_sequence<Is...>)
29{
30 // Performance considerations:
31 // The folding expression used here represents the same logic as a sequence of
32 // linear if/else if/... statements. Experiments show that Clang, GCC, and MSVC
33 // optimize it to essentially the same bytecode as a normal C++ switch,
34 // ensuring O(1) lookup complexity.
35 static_cast<void>(((Is == index ? (applier(std::integral_constant<size_t, Is>{}), true) : false)
36 || ...));
37}
38
39template <size_t IndexCount, typename Applier>
40void applyIndexSwitch(size_t index, Applier&& applier)
41{
42 applyIndexSwitch(index, std::forward<Applier>(applier), std::make_index_sequence<IndexCount>());
43}
44
45template <typename Interface>
47{
48private:
49 template <typename Arg>
50 static constexpr bool passArgAsValue = sizeof(Arg) <= sizeof(size_t)
52
53 template <typename ...>
54 struct MethodImpl;
55
56 template <typename M, typename R, typename I, typename... Args>
57 struct MethodImpl<M, R, I, Args...>
58 {
59 static_assert(std::is_base_of_v<I, Interface>, "The method must belong to the interface");
60 using return_type = R;
61 using call_args = std::tuple<std::conditional_t<passArgAsValue<Args>, Args, Args&&>...>;
62
63 static constexpr size_t index()
64 {
65 return index(std::make_index_sequence<std::tuple_size_v<Methods<>>>());
66 }
67
68 private:
69 template <size_t Ix>
70 static constexpr bool matchesAt()
71 {
72 return std::is_base_of_v<M, std::tuple_element_t<Ix, Methods<>>>;
73 }
74
75 template <size_t... Is>
76 static constexpr size_t index(std::index_sequence<Is...>)
77 {
78 constexpr size_t matchesCount = (size_t(matchesAt<Is>()) + ...);
79 static_assert(matchesCount == 1, "Expected exactly one match");
80 return ((size_t(matchesAt<Is>()) * Is) + ...);
81 }
82
83 static R invoke(I &intf /*const validation*/, Args... args)
84 {
85 Q_ASSERT(intf.m_callFN);
86
87 auto& baseIntf = static_cast<base_interface&>(const_cast<std::remove_const_t<I>&>(intf));
88 call_args callArgs(std::forward<Args>(args)...);
89 if constexpr (std::is_void_v<R>) {
90 intf.m_callFN(index(), baseIntf, nullptr, &callArgs);
91 } else {
92 alignas(R) std::byte buf[sizeof(R)];
93 intf.m_callFN(index(), baseIntf, buf, &callArgs);
94
95 R* result = std::launder(reinterpret_cast<R*>(buf));
96 QScopeGuard destroyBuffer([result]() { std::destroy_at(result); });
97 return std::forward<R>(*result);
98 }
99 }
100
101 friend class QQuasiVirtualInterface<Interface>;
102 };
103
104 template <typename M, typename R, typename I, typename... Args>
105 struct MethodImpl<M, R(I::*)(Args...)> : MethodImpl<M, R, I, Args...> {
106 template <typename Subclass>
107 using Overridden = R(Subclass::*)(Args...);
108 };
109
110 template <typename M, typename R, typename I, typename... Args>
111 struct MethodImpl<M, R(I::*)(Args...) const> : MethodImpl<M, R, const I, Args...> {
112 template <typename Subclass>
113 using Overridden = R(Subclass::*)(Args...) const;
114 };
115
116 template <typename C = Interface> using Methods = typename C::template MethodTemplates<C>;
117
118public:
119 template <auto prototype>
120 struct Method : MethodImpl<Method<prototype>, decltype(prototype)> {};
121
122 template <typename Method, typename... Args>
123 auto call(Args &&... args) const
124 {
125 return Method::invoke(static_cast<const Interface &>(*this), std::forward<Args>(args)...);
126 }
127
128 template <typename Method, typename... Args>
129 auto call(Args &&... args)
130 {
131 return Method::invoke(static_cast<Interface &>(*this), std::forward<Args>(args)...);
132 }
133
134 void destroy(); // quasi-virtual pure destructor
136
137 struct Deleter
138 {
139 void operator () (QQuasiVirtualInterface* self) const { self->call<Destroy>(); }
140 };
141
142protected:
144 using CallFN = void (*)(size_t index, base_interface &intf, void *ret, void *args);
145 void initCallFN(CallFN func) { m_callFN = func; }
146
149
150private:
152 CallFN m_callFN = nullptr;
153};
154
155template <typename Subclass, typename Interface>
156class QQuasiVirtualSubclass : public Interface
157{
158private:
159 template <typename C = Subclass> using Methods = typename C::template MethodTemplates<C>;
160
161 template <size_t OverriddenIndex>
162 static constexpr size_t interfaceMethodIndex() {
163 return std::tuple_element_t<OverriddenIndex, Methods<>>::index();
164 }
165
166 template <size_t... Is>
167 static void callImpl(size_t index, Subclass &subclass, void *ret, void *args, std::index_sequence<Is...>)
168 {
169 constexpr auto methodIndexMask = []() {
170 std::array<bool, sizeof...(Is)> result = {};
171 (static_cast<void>(std::get<interfaceMethodIndex<Is>()>(result) = true), ...);
172 return result;
173 }();
174 static_assert((methodIndexMask[Is] && ...),
175 "Mapping between base and overridden methods is not unique");
176
177 auto doInvoke = [&](auto idxConstant) {
178 std::tuple_element_t<idxConstant.value, Methods<>>::doInvoke(subclass, ret, args);
179 };
180 applyIndexSwitch(index, doInvoke, std::index_sequence<interfaceMethodIndex<Is>()...>{});
181 }
182
183 static void callImpl(size_t index, typename Interface::base_interface &intf, void *ret, void *args)
184 {
185 constexpr auto seq = std::make_index_sequence<std::tuple_size_v<Methods<>>>();
186 callImpl(index, static_cast<Subclass&>(intf), ret, args, seq);
187 }
188
189 template <typename BaseMethod>
190 using OverridenSignature = typename BaseMethod::template Overridden<Subclass>;
191
192protected:
193 template <typename... Args>
194 QQuasiVirtualSubclass(Args &&... args)
195 : Interface(std::forward<Args>(args)...)
196 {
197 Interface::initCallFN(&QQuasiVirtualSubclass::callImpl);
198 }
199
200public:
201 template <typename BaseMethod, OverridenSignature<BaseMethod> overridden>
202 struct Override : BaseMethod
203 {
204 private:
205 static constexpr void doInvoke(Subclass &subclass, void *ret, void *args)
206 {
207 using Return = typename BaseMethod::return_type;
208 using PackedArgs = typename BaseMethod::call_args;
209
210 Q_ASSERT(args);
211 Q_ASSERT(std::is_void_v<Return> == !ret);
212
213 auto invoke = [&subclass](auto &&...params)
214 {
215 return std::invoke(overridden, &subclass, std::forward<decltype(params)>(params)...);
216 };
217
218 if constexpr (std::is_void_v<Return>) {
219 std::apply(invoke, std::move(*static_cast<PackedArgs *>(args)));
220 } else {
221 // Note, that ::new (*) Return(...) fails on Integrity.
222 // TODO: use std::construct_at for c++20
223 using Alloc = std::allocator<Return>;
224 Alloc alloc;
225 std::allocator_traits<Alloc>::construct(alloc, static_cast<Return *>(ret),
226 std::apply(invoke, std::move(*static_cast<PackedArgs *>(args))));
227 }
228
229 }
230
231 friend class QQuasiVirtualSubclass<Subclass, Interface>;
232 };
233};
234
235} // namespace QtPrivate
236
237QT_END_NAMESPACE
238
239#endif // Q_DOC
240
241#endif // QQUASIVIRTUAL_IMPL_H
Method<&QQuasiVirtualInterface::destroy > Destroy
auto call(Args &&... args) const
QQuasiVirtualInterface< Interface > base_interface
void(*)(size_t index, base_interface &intf, void *ret, void *args) CallFN
void applyIndexSwitch(size_t index, Applier &&applier, std::index_sequence< Is... >)
void applyIndexSwitch(size_t index, Applier &&applier)
void operator()(QQuasiVirtualInterface *self) const