diff options
-rw-r--r-- | src/corelib/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/corelib/global/qxpfunctional.h | 176 | ||||
-rw-r--r-- | tests/auto/corelib/global/CMakeLists.txt | 1 | ||||
-rw-r--r-- | tests/auto/corelib/global/qxp/CMakeLists.txt | 1 | ||||
-rw-r--r-- | tests/auto/corelib/global/qxp/function_ref/CMakeLists.txt | 7 | ||||
-rw-r--r-- | tests/auto/corelib/global/qxp/function_ref/tst_qxp_function_ref.cpp | 234 |
6 files changed, 420 insertions, 0 deletions
diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt index a802e1140d..47b9bd0d11 100644 --- a/src/corelib/CMakeLists.txt +++ b/src/corelib/CMakeLists.txt @@ -73,6 +73,7 @@ qt_internal_add_module(Core global/q20algorithm.h global/q20functional.h global/q23functional.h + global/qxpfunctional.h global/q20iterator.h io/qabstractfileengine.cpp io/qabstractfileengine_p.h io/qbuffer.cpp io/qbuffer.h diff --git a/src/corelib/global/qxpfunctional.h b/src/corelib/global/qxpfunctional.h new file mode 100644 index 0000000000..67350c56ed --- /dev/null +++ b/src/corelib/global/qxpfunctional.h @@ -0,0 +1,176 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +#ifndef QXPFUNCTIONAL_H +#define QXPFUNCTIONAL_H + +#include <QtCore/qglobal.h> + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. Types and functions defined +// in this file will behave exactly as their std counterparts. You +// may use these definitions in your own code, but be aware that we +// will remove them once Qt depends on the C++ version that supports +// them in namespace std. There will be NO deprecation warning, the +// definitions will JUST go away. +// +// If you can't agree to these terms, don't use these definitions! +// +// We mean it. +// + +#include <QtCore/q23functional.h> +#include <type_traits> +#include <utility> + +QT_BEGIN_NAMESPACE + +namespace qxp { +// like P0792r9's function_ref: + +// [func.wrap.ref], non-owning wrapper +template<class... S> class function_ref; // not defined + +// template<class R, class... ArgTypes> +// class function_ref<R(ArgTypes...) cv noexcept(noex)>; // see below +// +// [func.wrap.ref.general] +// The header provides partial specializations of function_ref for each combination +// of the possible replacements of the placeholders cv and noex where: +// - cv is either const or empty. +// - noex is either true or false. + +namespace detail { + +template <typename T> +using if_function = std::enable_if_t<std::is_function_v<T>, bool>; +template <typename T> +using if_non_function = std::enable_if_t<!std::is_function_v<T>, bool>; + +template <typename From, typename To> +using copy_const_t = std::conditional_t< + std::is_const_v<From>, + std::add_const_t<To>, + To + >; + +template <class Const> +union BoundEntityType { + template <typename F, if_function<F> = true> + explicit constexpr BoundEntityType(F *f) + : fun(reinterpret_cast<QFunctionPointer>(f)) {} + template <typename T, if_non_function<T> = true> + explicit constexpr BoundEntityType(T *t) + : obj(static_cast<Const*>(t)) {} + Const *obj; + QFunctionPointer fun; +}; + +template <bool noex, class Const, class R, class... ArgTypes> +class function_ref_base +{ +protected: + ~function_ref_base() = default; + + using BoundEntityType = detail::BoundEntityType<Const>; + + template <typename... Ts> + using is_invocable_using = std::conditional_t< + noex, + std::is_nothrow_invocable_r<R, Ts..., ArgTypes...>, + std::is_invocable_r<R, Ts..., ArgTypes...> + >; + + using ThunkPtr = R(*)(BoundEntityType, ArgTypes&&...) noexcept(noex); + + BoundEntityType m_bound_entity; + ThunkPtr m_thunk_ptr; + +public: + template< + class F, + std::enable_if_t<std::conjunction_v< + std::is_function<F>, + is_invocable_using<F> + >, bool> = true + > + Q_IMPLICIT function_ref_base(F* f) noexcept + : m_bound_entity(f), + m_thunk_ptr([](BoundEntityType ctx, ArgTypes&&... args) noexcept(noex) -> R { + return q23::invoke_r<R>(reinterpret_cast<F*>(ctx.fun), + std::forward<ArgTypes>(args)...); + }) + {} + + template< + class F, + std::enable_if_t<std::conjunction_v< + std::negation<std::is_same<q20::remove_cvref_t<F>, function_ref_base>>, + std::negation<std::is_member_pointer<std::remove_reference_t<F>>>, + is_invocable_using<copy_const_t<Const, std::remove_reference_t<F>>&> + >, bool> = true + > + Q_IMPLICIT constexpr function_ref_base(F&& f) noexcept + : m_bound_entity(std::addressof(f)), + m_thunk_ptr([](BoundEntityType ctx, ArgTypes&&... args) noexcept(noex) -> R { + using That = copy_const_t<Const, std::remove_reference_t<F>>; + return q23::invoke_r<R>(*static_cast<That*>(ctx.obj), + std::forward<ArgTypes>(args)...); + }) + {} + +protected: + template < + class T, + std::enable_if_t<std::conjunction_v< + std::negation<std::is_same<q20::remove_cvref_t<T>, function_ref_base>>, + std::negation<std::is_pointer<T>> + >, bool> = true + > + function_ref_base& operator=(T) = delete; + + // Invocation [func.wrap.ref.inv] + R operator()(ArgTypes... args) const noexcept(noex) + { + return m_thunk_ptr(m_bound_entity, std::forward<ArgTypes>(args)...); + } + +}; + +} // namespace detail + +#define QT_SPECIALIZE_FUNCTION_REF(cv, noex) \ + template<class R, class... ArgTypes> \ + class function_ref<R(ArgTypes...) cv noexcept( noex )> \ + : private detail::function_ref_base< noex , cv void, R, ArgTypes...> \ + { \ + using base = detail::function_ref_base< noex , cv void, R, ArgTypes...>; \ + \ + public: \ + using base::base; \ + using base::operator(); \ + } \ + /* end */ + +QT_SPECIALIZE_FUNCTION_REF( , false); +QT_SPECIALIZE_FUNCTION_REF(const, false); +QT_SPECIALIZE_FUNCTION_REF( , true ); +QT_SPECIALIZE_FUNCTION_REF(const, true ); + +#undef QT_SPECIALIZE_FUNCTION_REF + +// deduction guides [func.wrap.ref.deduct] + +template < + class F, + std::enable_if_t<std::is_function_v<F>, bool> = true +> +function_ref(F*) -> function_ref<F>; + +} // namespace qxp + +QT_END_NAMESPACE + +#endif /* QXPFUNCTIONAL_H */ diff --git a/tests/auto/corelib/global/CMakeLists.txt b/tests/auto/corelib/global/CMakeLists.txt index fe63402288..e19eab18eb 100644 --- a/tests/auto/corelib/global/CMakeLists.txt +++ b/tests/auto/corelib/global/CMakeLists.txt @@ -21,3 +21,4 @@ add_subdirectory(qoperatingsystemversion) if(WIN32) add_subdirectory(qwinregistry) endif() +add_subdirectory(qxp) diff --git a/tests/auto/corelib/global/qxp/CMakeLists.txt b/tests/auto/corelib/global/qxp/CMakeLists.txt new file mode 100644 index 0000000000..f289a04701 --- /dev/null +++ b/tests/auto/corelib/global/qxp/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(function_ref) diff --git a/tests/auto/corelib/global/qxp/function_ref/CMakeLists.txt b/tests/auto/corelib/global/qxp/function_ref/CMakeLists.txt new file mode 100644 index 0000000000..91c797c32f --- /dev/null +++ b/tests/auto/corelib/global/qxp/function_ref/CMakeLists.txt @@ -0,0 +1,7 @@ +qt_internal_add_test(tst_qxp_function_ref + EXCEPTIONS + SOURCES + tst_qxp_function_ref.cpp + PUBLIC_LIBRARIES + Qt::Core +) diff --git a/tests/auto/corelib/global/qxp/function_ref/tst_qxp_function_ref.cpp b/tests/auto/corelib/global/qxp/function_ref/tst_qxp_function_ref.cpp new file mode 100644 index 0000000000..b17ead436a --- /dev/null +++ b/tests/auto/corelib/global/qxp/function_ref/tst_qxp_function_ref.cpp @@ -0,0 +1,234 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include <QtCore/qxpfunctional.h> + +#include <QTest> + +#include <type_traits> + +// checking dependency q20::remove_cvref_t: +#define CHECK(in, out) \ + static_assert(std::is_same_v<q20::remove_cvref_t< in >, out >) +CHECK(int, int); +CHECK(const int, int); +CHECK(int &, int); +CHECK(const int &, int); +CHECK(int &&, int); +CHECK(const int &&, int); +CHECK(int *, int *); +CHECK(const int *, const int *); +CHECK(int[4], int[4]); +CHECK(const int (&)[4], int[4]); +#undef CHECK + +template <typename T> constexpr inline bool +is_noexcept_function_ref_helper_v = false; +template <typename R, typename...Args> constexpr inline bool +is_noexcept_function_ref_helper_v<qxp::function_ref<R(Args...) noexcept(true)>> = true; +template <typename R, typename...Args> constexpr inline bool +is_noexcept_function_ref_helper_v<qxp::function_ref<R(Args...) const noexcept(true)>> = true; + +template <typename T> constexpr inline bool +is_noexcept_function_ref_v = is_noexcept_function_ref_helper_v<q20::remove_cvref_t<T>>; + +class tst_qxp_function_ref : public QObject +{ + Q_OBJECT +public: + using QObject::QObject; + +private Q_SLOTS: + void basics(); + void constOverloads(); + void constExpr(); + void ctad(); +}; + +void tst_qxp_function_ref::basics() +{ + static_assert(std::is_trivially_copyable_v<qxp::function_ref<int(int)>>); + static_assert(std::is_trivially_copyable_v<qxp::function_ref<int()>>); + static_assert(std::is_trivially_copyable_v<qxp::function_ref<void()>>); + + { + Q_CONSTINIT static int invoked = 0; + auto lambda = [](int i) noexcept { ++invoked; return i; }; + const qxp::function_ref<int(int)> f = lambda; + QCOMPARE(invoked, 0); + QCOMPARE(f(42), 42); + QCOMPARE(invoked, 1); + + const int fourtyTwo = 42; + + const qxp::function_ref<int(int) noexcept> f2 = std::move(lambda); + QCOMPARE(invoked, 1); + QCOMPARE(f2(fourtyTwo), 42); + QCOMPARE(invoked, 2); + + int (*fpr)(int) = lambda; + + const qxp::function_ref f3 = fpr; + static_assert(!is_noexcept_function_ref_v<decltype(f3)>); + QCOMPARE(invoked, 2); + QCOMPARE(f3(42), 42); + QCOMPARE(invoked, 3); + + int (*fpr2)(int) noexcept = lambda; + + const qxp::function_ref f4 = fpr2; + static_assert(is_noexcept_function_ref_v<decltype(f4)>); + QCOMPARE(invoked, 3); + QCOMPARE(f4(42), 42); + QCOMPARE(invoked, 4); + } + { + Q_CONSTINIT static int invoked = 0; + auto lambda = [] { ++invoked; return 42; }; + const qxp::function_ref<int()> f = lambda; + QCOMPARE(invoked, 0); + QCOMPARE(f(), 42); + QCOMPARE(invoked, 1); + + const qxp::function_ref<int()> f2 = std::move(lambda); + QCOMPARE(invoked, 1); + QCOMPARE(f2(), 42); + QCOMPARE(invoked, 2); + + int (*fpr)() = lambda; + + const qxp::function_ref f3 = fpr; + static_assert(!is_noexcept_function_ref_v<decltype(f3)>); + QCOMPARE(invoked, 2); + QCOMPARE(f3(), 42); + QCOMPARE(invoked, 3); + } + { + Q_CONSTINIT static int invoked = 0; + auto lambda = [] { ++invoked; }; + const qxp::function_ref<void()> f = lambda; + QCOMPARE(invoked, 0); + f(); + QCOMPARE(invoked, 1); + + const qxp::function_ref<void()> f2 = std::move(lambda); + QCOMPARE(invoked, 1); + f2(); + QCOMPARE(invoked, 2); + + void (*fpr)() = lambda; + + const qxp::function_ref f3 = fpr; + QCOMPARE(invoked, 2); + f3(); + QCOMPARE(invoked, 3); + } +} + +void tst_qxp_function_ref::constOverloads() +{ + auto func_c = [](qxp::function_ref<int() const> callable) + { + return callable(); + }; + auto func_m = [](qxp::function_ref<int() /*mutable*/> callable) + { + return callable(); + }; + + struct S + { + int operator()() { return 1; } + int operator()() const { return 2; } + }; + S s; + QCOMPARE(func_c(s), 2); + QCOMPARE(func_m(s), 1); + const S cs; + QCOMPARE(func_c(cs), 2); +#if 0 + // this should not compile (and doesn't, but currently fails with an error in the impl, + // not by failing a constructor constaint → spec issue?). + QCOMPARE(func_m(cs), 2); +#endif +} + +void tst_qxp_function_ref::constExpr() +{ + Q_CONSTINIT static int invoked = 0; + { + Q_CONSTINIT static auto lambda = [] (int i) { ++invoked; return i; }; + // the function object constructor is constexpr, so this should be constinit: + Q_CONSTINIT static qxp::function_ref<int(int)> f = lambda; + + QCOMPARE(invoked, 0); + QCOMPARE(f(15), 15); + QCOMPARE(invoked, 1); + } + { + constexpr static auto lambda = [] (int i) { ++invoked; return i; }; + // the function object constructor is constexpr, so this should be constinit: + Q_CONSTINIT static qxp::function_ref<int(int) const> f = lambda; + + QCOMPARE(invoked, 1); + QCOMPARE(f(51), 51); + QCOMPARE(invoked, 2); + +#if 0 // ### should this work?: + Q_CONSTINIT static qxp::function_ref<int(int)> f2 = lambda; + + QCOMPARE(invoked, 2); + QCOMPARE(f(150), 150); + QCOMPARE(invoked, 3); +#endif + + } +} + +int i_f_i_nx(int i) noexcept { return i; } +void v_f_i_nx(int) noexcept {} +int i_f_v_nx() noexcept { return 42; } +void v_f_v_nx() noexcept {} + +int i_f_i_ex(int i) { return i; } +void v_f_i_ex(int) {} +int i_f_v_ex() { return 42; } +void v_f_v_ex() {} + +void tst_qxp_function_ref::ctad() +{ +#define CHECK(fun, sig) \ + do { \ + qxp::function_ref f = fun; \ + static_assert(std::is_same_v<decltype(f), \ + qxp::function_ref<sig>>); \ + qxp::function_ref f2 = &fun; \ + static_assert(std::is_same_v<decltype(f2), \ + qxp::function_ref<sig>>); \ + } while (false) + + CHECK(i_f_i_nx, int (int) noexcept); + CHECK(v_f_i_nx, void(int) noexcept); + CHECK(i_f_v_nx, int ( ) noexcept); + CHECK(v_f_v_nx, void( ) noexcept); + + CHECK(i_f_i_ex, int (int)); + CHECK(v_f_i_ex, void(int)); + CHECK(i_f_v_ex, int ( )); + CHECK(v_f_v_ex, void( )); + +#undef CHECK + +#if 0 // no deduction guides for the non-function-pointer case, so no CTAD for lambdas + { + qxp::function_ref f = [](int i) -> int { return i; }; + static_assert(std::is_same_v<decltype(f), + qxp::function_ref<int(int)>>); + } +#endif +} + + +QTEST_APPLESS_MAIN(tst_qxp_function_ref); + +#include "tst_qxp_function_ref.moc" |