Short live qxp::function_ref!
This is an implementation of function_ref, which has been proposed for inclusion into C++23, but has not been accepted, yet, which is why we place it in namespace qxp (for eXPerimental) instead of q23. The implementation is based on wg21.link/P0792r9, which, at the time of writing, is the latest revision of the paper. It will be used in both QTestLib and qmldom. Fixes: QTBUG-103739 Change-Id: I52723eca28f7ac02ce7ce51928361d81ae5c92b1 Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io> Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
This commit is contained in:
parent
4042596b72
commit
29b65c98e7
@ -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
|
||||
|
176
src/corelib/global/qxpfunctional.h
Normal file
176
src/corelib/global/qxpfunctional.h
Normal file
@ -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 */
|
@ -21,3 +21,4 @@ add_subdirectory(qoperatingsystemversion)
|
||||
if(WIN32)
|
||||
add_subdirectory(qwinregistry)
|
||||
endif()
|
||||
add_subdirectory(qxp)
|
||||
|
1
tests/auto/corelib/global/qxp/CMakeLists.txt
Normal file
1
tests/auto/corelib/global/qxp/CMakeLists.txt
Normal file
@ -0,0 +1 @@
|
||||
add_subdirectory(function_ref)
|
@ -0,0 +1,7 @@
|
||||
qt_internal_add_test(tst_qxp_function_ref
|
||||
EXCEPTIONS
|
||||
SOURCES
|
||||
tst_qxp_function_ref.cpp
|
||||
PUBLIC_LIBRARIES
|
||||
Qt::Core
|
||||
)
|
@ -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"
|
Loading…
Reference in New Issue
Block a user