• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

DNKpp / mimicpp / 18446180060

12 Oct 2025 03:53PM UTC coverage: 97.974% (-0.1%) from 98.107%
18446180060

Pull #134

github

web-flow
Merge 946130c91 into 798c20ab0
Pull Request #134: Feature: New set of facade-macros

29 of 33 new or added lines in 2 files covered. (87.88%)

1 existing line in 1 file now uncovered.

2466 of 2517 relevant lines covered (97.97%)

12639.09 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.0
/include/mimic++/Facade.hpp
1
//          Copyright Dominic (DNKpp) Koepke 2024 - 2025.
2
// Distributed under the Boost Software License, Version 1.0.
3
//    (See accompanying file LICENSE_1_0.txt or copy at
4
//          https://www.boost.org/LICENSE_1_0.txt)
5

6
#ifndef MIMICPP_FACADE_HPP
7
#define MIMICPP_FACADE_HPP
8

9
#pragma once
10

11
#include "mimic++/Fwd.hpp"
12
#include "mimic++/Mock.hpp"
13
#include "mimic++/config/Config.hpp"
14
#include "mimic++/macros/Facade.hpp"
15
#include "mimic++/macros/InterfaceMocking.hpp"
16
#include "mimic++/printing/TypePrinter.hpp"
17
#include "mimic++/utilities/StaticString.hpp"
18

19
#ifndef MIMICPP_DETAIL_IS_MODULE
20
    #include <cstddef>
21
    #include <iterator>
22
    #include <tuple>
23
    #include <type_traits>
24
    #include <utility>
25
#endif
26

27
namespace mimicpp
28
{
29
    /**
30
     * \defgroup FACADE facade
31
     * \brief Contains utility to simplify the process of generating forwarding facade functions.
32
     *
33
     * \details
34
     * While this library tries avoiding macros when possible, sometimes we must not be too stubborn.
35
     * Making interface mocking more enjoyable is such a situation.
36
     * While this can, of course, be done without macros, this quickly becomes annoying, due to the necessary boilerplate code.
37
     * \snippet FacadeMock.cpp facade mock manual
38
     *
39
     * *mimic++* therefore introduces several macros, which helps to reduce the effort to a minimum.
40
     * With them, the boilerplate can be reduced to this macro invocation, which effectively does the same as before:
41
     * ```cpp
42
     * MAKE_MEMBER_METHOD(foo, void, (), override);
43
     * ```
44
     *
45
     * The good news is that these macros are just a thin layer around the macro-free core and can thus be easily avoided.
46
     * Nevertheless, *mimic++* still aims to become as macro-less as possible.
47
     * As soon as reflection becomes available,
48
     * an attempt will be made to solve this feature completely in the C++ language (hopefully with c++26, but only time will tell).
49
     *
50
     * ## Multiple inheritance
51
     *
52
     * This use-case is fully supported, without any special tricks.
53
     * \snippet FacadeMock.cpp facade mock multiple inheritance
54
     *
55
     * ## Mocks and variadic templates
56
     *
57
     * Due to the nature of the `mimicpp::Mock` design, it directly supports packs without any question.
58
     * \snippet VariadicMocks.cpp variadic mock def
59
     * \snippet VariadicMocks.cpp variadic mock
60
     *
61
     * The interesting part is: Do the facade macros also support variadic templates?
62
     *
63
     * Yes they do! Both handle packs correctly.
64
     * \snippet VariadicMocks.cpp variadic interface def
65
     *
66
     * They can then be used with arbitrary template arguments.
67
     * \snippet VariadicMocks.cpp variadic interface zero
68
     * \snippet VariadicMocks.cpp variadic interface 2
69
     *
70
     * \see A more detailed explanation about the internals can be found here
71
     * https://dnkpp.github.io/2024-12-15-simultaneous-pack-expansion-inside-macros/
72
     */
73
}
74

75
namespace mimicpp::facade::detail
76
{
77
    // * the generated facade implementation - lambda
78
    // * the generated facade implementation
79
    inline constexpr std::size_t facadeBaseCallDepth{2u};
80

81
    template <typename Self>
82
    [[nodiscard]]
83
    StringT generate_member_target_name(StringViewT const functionName)
84
    {
85
        StringStreamT ss{};
658✔
86
        mimicpp::print_type<Self>(std::ostreambuf_iterator{ss});
658✔
87
        ss << "::" << functionName;
658✔
88

89
        return std::move(ss).str();
1,316✔
90
    }
658✔
91

92
    template <typename Signature, typename Target>
93
    [[nodiscard]]
94
    constexpr auto&& forward_for(Target& target) noexcept
95
    {
96
        using ref = std::conditional_t<
97
            ValueCategory::rvalue == signature_ref_qualification_v<Signature>,
98
            Target&&,
99
            Target&>;
100

101
        return static_cast<ref>(target);
320✔
102
    }
103

104
    // * detail::apply::lambda
105
    // * detail::apply
106
    inline constexpr std::size_t applyCallDepth{2u};
107

108
    /**
109
     * \brief Applies the given args on the target.
110
     * \details
111
     * This function casts the provided target to an appropriate reference for the specified signature.
112
     * Additionally, it does exactly the same as `std::apply`, with the important difference that we reliably
113
     * know the call depth, which does vary for `std::apply` depending on the concrete implementation.
114
     */
115
    template <typename Signature, typename... Args>
116
    constexpr decltype(auto) apply(auto& target, std::tuple<Args...>&& args)
117
    {
118
        return [&]<std::size_t... indices>([[maybe_unused]] std::index_sequence<indices...> const) -> decltype(auto) {
778✔
119
            return forward_for<Signature>(target)(
175✔
120
                std::forward<Args>(std::get<indices>(args))...);
295✔
121
        }(std::index_sequence_for<Args...>{});
328✔
122
    }
123
}
124

125
MIMICPP_DETAIL_MODULE_EXPORT namespace mimicpp::facade
126
{
127
    template <template <typename...> typename TargetTemplate>
128
    struct basic_as_member
129
    {
130
        static constexpr bool is_member{true};
131

132
        template <typename... Signatures>
133
        using target_type = TargetTemplate<Signatures...>;
134

135
        template <typename Signature, typename... Args>
136
        static constexpr decltype(auto) invoke(
137
            auto& target,
138
            [[maybe_unused]] auto* const self,
139
            std::tuple<Args...>&& args)
140
        {
141
            return detail::apply<Signature>(target, std::move(args));
116✔
142
        }
143

144
        template <typename Self>
145
        [[nodiscard]]
146
        static constexpr MockSettings make_settings([[maybe_unused]] Self const* const self, StringViewT const functionName)
147
        {
148
            constexpr std::size_t skip = 1u + detail::facadeBaseCallDepth + detail::applyCallDepth;
216✔
149

150
            return MockSettings{
151
                .name = detail::generate_member_target_name<Self>(functionName),
216✔
152
                .stacktraceSkip = skip};
216✔
153
        }
216✔
154
    };
155

156
    using mock_as_member = basic_as_member<Mock>;
157

158
    template <typename Self, template <typename...> typename TargetTemplate>
159
    struct basic_as_member_with_this
160
    {
161
        static constexpr bool is_member{true};
162

163
        template <typename Signature, bool isConst = Constness::as_const == signature_const_qualification_v<Signature>>
164
        using prepend_this = signature_prepend_param_t<
165
            Signature,
166
            std::conditional_t<isConst, Self const*, Self*>>;
167

168
        template <typename... Signatures>
169
        using target_type = TargetTemplate<prepend_this<Signatures>...>;
170

171
        template <typename Signature, typename... Args>
172
        static constexpr decltype(auto) invoke(auto& target, auto* const self, std::tuple<Args...>&& args)
173
        {
174
            return detail::apply<Signature>(
27✔
175
                target,
176
                std::tuple_cat(std::make_tuple(self), std::move(args)));
54✔
177
        }
178

179
        [[nodiscard]]
180
        static constexpr MockSettings make_settings([[maybe_unused]] auto const* const self, StringViewT const functionName)
181
        {
182
            constexpr std::size_t skip = 1u + detail::facadeBaseCallDepth + detail::applyCallDepth;
149✔
183

184
            return MockSettings{
185
                .name = detail::generate_member_target_name<Self>(functionName),
149✔
186
                .stacktraceSkip = skip};
149✔
187
        }
149✔
188
    };
189

190
    template <typename Self>
191
    using mock_as_member_with_this = basic_as_member_with_this<Self, Mock>;
192
}
193

194
// These symbols are called from within "exported" macros and must thus be visible to the caller.
195
MIMICPP_DETAIL_MODULE_EXPORT namespace mimicpp::facade::detail
196
{
197
    template <typename Traits>
198
    inline constexpr bool is_member_v = false;
199

200
    template <typename Traits>
201
        requires requires { Traits::is_member; }
202
    inline constexpr bool is_member_v<Traits>{Traits::is_member};
203

204
    template <auto specText>
205
    struct apply_normalized_specs
206
    {
207
        [[nodiscard]]
208
        static consteval auto evaluate_specs()
209
        {
210
            constexpr std::string_view constKeyword{"const"};
211
            constexpr std::string_view noexceptKeyword{"noexcept"};
212
            constexpr std::string_view overrideKeyword{"override"};
213
            constexpr std::string_view finalKeyword{"final"};
214

215
            auto const end = std::ranges::end(specText);
216
            auto const find_token_begin = [&](auto const first) noexcept {
217
                constexpr auto is_space = [](char const c) noexcept {
NEW
218
                    return ' ' == c || '\t' == c;
×
219
                };
220

NEW
221
                return std::ranges::find_if_not(first, end, is_space);
×
222
            };
223

224
            struct spec_info
225
            {
226
                bool hasConst{false};
227
                ValueCategory refQualifier = ValueCategory::any;
228
                bool hasNoexcept{false};
229
            };
230

231
            spec_info result{};
232
            for (auto tokenBegin = find_token_begin(std::ranges::begin(specText));
233
                 tokenBegin != end;
234
                 tokenBegin = find_token_begin(tokenBegin))
235
            {
236
                if ('&' == *tokenBegin)
237
                {
238
                    MIMICPP_ASSERT(result.refQualifier == ValueCategory::any, "Ref-qualifier already set.");
239
                    if (++tokenBegin != end
240
                        && '&' == *tokenBegin)
241
                    {
242
                        ++tokenBegin;
243
                        result.refQualifier = ValueCategory::rvalue;
244
                    }
245
                    else
246
                    {
247
                        result.refQualifier = ValueCategory::lvalue;
248
                    }
249
                }
250
                else
251
                {
252
                    constexpr auto is_word_continue = [](char const c) noexcept {
NEW
253
                        return ('a' <= c && c <= 'z')
×
NEW
254
                            || ('A' <= c && c <= 'Z');
×
255
                    };
256

257
                    auto const tokenEnd = std::ranges::find_if_not(tokenBegin, end, is_word_continue);
258
                    std::string_view const token{tokenBegin, tokenEnd};
259
                    if (constKeyword == token)
260
                    {
261
                        MIMICPP_ASSERT(!result.hasConst, "Const-qualifier already set.");
262
                        result.hasConst = true;
263
                    }
264
                    else if (noexceptKeyword == token)
265
                    {
266
                        MIMICPP_ASSERT(!result.hasNoexcept, "Noexcept-qualifier already set.");
267
                        result.hasNoexcept = true;
268
                    }
269
                    else if (overrideKeyword != token && finalKeyword != token)
270
                    {
271
                        throw std::runtime_error{"Invalid spec"};
272
                    }
273

274
                    tokenBegin = tokenEnd;
275
                }
276
            }
277

278
            return result;
279
        }
280

281
        static constexpr auto info = evaluate_specs();
282

283
    public:
284
        template <typename Signature>
285
        [[nodiscard]]
286
        static consteval auto evaluate() noexcept
287
        {
288
            using sig_maybe_ref = std::conditional_t<
289
                ValueCategory::lvalue == info.refQualifier,
290
                signature_add_lvalue_ref_qualifier_t<Signature>,
291
                std::conditional_t<
292
                    ValueCategory::rvalue == info.refQualifier,
293
                    signature_add_rvalue_ref_qualifier_t<Signature>,
294
                    Signature>>;
295

296
            using sig_maybe_const = std::conditional_t<
297
                info.hasConst,
298
                signature_add_const_qualifier_t<sig_maybe_ref>,
299
                sig_maybe_ref>;
300

301
            using sig_maybe_noexcept = std::conditional_t<
302
                info.hasNoexcept,
303
                signature_add_noexcept_t<sig_maybe_const>,
304
                sig_maybe_const>;
305

306
            return std::type_identity<sig_maybe_noexcept>{};
307
        }
308

309
        template <typename Signature>
310
        using type = decltype(evaluate<Signature>())::type;
311
    };
312

313
    template <typename RawSignature, auto specText>
314
    using apply_normalized_specs_t = apply_normalized_specs<specText>::template type<RawSignature>;
315
}
316

317
#endif
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc