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:
Marc Mutz 2022-05-24 08:46:22 +02:00
parent 4042596b72
commit 29b65c98e7
6 changed files with 420 additions and 0 deletions

View File

@ -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

View 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 */

View File

@ -21,3 +21,4 @@ add_subdirectory(qoperatingsystemversion)
if(WIN32)
add_subdirectory(qwinregistry)
endif()
add_subdirectory(qxp)

View File

@ -0,0 +1 @@
add_subdirectory(function_ref)

View File

@ -0,0 +1,7 @@
qt_internal_add_test(tst_qxp_function_ref
EXCEPTIONS
SOURCES
tst_qxp_function_ref.cpp
PUBLIC_LIBRARIES
Qt::Core
)

View File

@ -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"