170 lines
6.8 KiB
C++
170 lines
6.8 KiB
C++
/*
|
|
This file is part of KDBindings.
|
|
|
|
SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
|
|
Author: Leon Matthes <leon.matthes@kdab.com>
|
|
|
|
SPDX-License-Identifier: MIT
|
|
|
|
Contact KDAB at <info@kdab.com> for commercial licensing options.
|
|
*/
|
|
#pragma once
|
|
|
|
#include <functional>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
|
|
namespace KDBindings {
|
|
|
|
/**
|
|
* The contents of this namespace may only be accessed by the implementation of KDBindings, they
|
|
* are not part of KDBindings public API and may be altered at any time and provide no guarantees
|
|
* of any kind when used directly.
|
|
**/
|
|
namespace Private {
|
|
|
|
// ------------------------ get_arity --------------------------
|
|
// get_arity is a template function that returns the number of arguments
|
|
// of (almost) any callable object.
|
|
// The easiest way is to simply call get_arity<T>() for callable type T.
|
|
// It needs to be constexpr in order so it can be used in template arguments.
|
|
|
|
// To overload get_arity, it needs a marker type, as C++ doesn't allow partial
|
|
// function specialization.
|
|
template<typename T>
|
|
struct TypeMarker {
|
|
constexpr TypeMarker() = default;
|
|
};
|
|
|
|
// base implementation of get_arity refers to specialized implementations for each
|
|
// type of callable object by using the overload for its specialized TypeMarker.
|
|
template<typename T>
|
|
constexpr size_t get_arity()
|
|
{
|
|
return get_arity(TypeMarker<std::decay_t<T>>{});
|
|
}
|
|
|
|
// Syntactic sugar version of get_arity, allows to pass any callable object
|
|
// to get_arity, instead of having to pass its decltype as a template argument.
|
|
template<typename T>
|
|
constexpr size_t get_arity(const T &)
|
|
{
|
|
return get_arity<T>();
|
|
}
|
|
|
|
// The arity of a function pointer is simply its number of arguments.
|
|
template<typename Return, typename... Arguments>
|
|
constexpr size_t get_arity(TypeMarker<Return (*)(Arguments...)>)
|
|
{
|
|
return sizeof...(Arguments);
|
|
}
|
|
|
|
template<typename Return, typename... Arguments>
|
|
constexpr size_t get_arity(TypeMarker<Return (*)(Arguments...) noexcept>)
|
|
{
|
|
return sizeof...(Arguments);
|
|
}
|
|
|
|
// The arity of a generic callable object is the arity of its operator() - 1, as the this
|
|
// pointer is already known for such an object.
|
|
template<typename T>
|
|
constexpr size_t get_arity(TypeMarker<T>)
|
|
{
|
|
return get_arity(TypeMarker<decltype(&T::operator())>{}) - 1;
|
|
}
|
|
|
|
// Macro to help define most combinations of possible member function qualifiers.
|
|
// Add + 1 to sizeof...(Arguments) here as the "this" pointer is an implicit argument to any member function.
|
|
#define KDBINDINGS_DEFINE_MEMBER_GET_ARITY(MODIFIERS) \
|
|
template<typename Return, typename Class, typename... Arguments> \
|
|
constexpr size_t get_arity(::KDBindings::Private::TypeMarker<Return (Class::*)(Arguments...) MODIFIERS>) \
|
|
{ \
|
|
return sizeof...(Arguments) + 1; \
|
|
}
|
|
|
|
// Define the get_arity version without modifiers without using the macro.
|
|
// MSVC otherwise complains about a call to the macro with too few arguments
|
|
template<typename Return, typename Class, typename... Arguments>
|
|
constexpr size_t get_arity(::KDBindings::Private::TypeMarker<Return (Class::*)(Arguments...)>)
|
|
{
|
|
return sizeof...(Arguments) + 1;
|
|
}
|
|
|
|
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(const)
|
|
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(&)
|
|
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(const &)
|
|
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(&&)
|
|
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(const &&)
|
|
|
|
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile)
|
|
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile const)
|
|
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile &)
|
|
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile const &)
|
|
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile &&)
|
|
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile const &&)
|
|
|
|
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(noexcept)
|
|
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(const noexcept)
|
|
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(&noexcept)
|
|
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(const &noexcept)
|
|
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(&&noexcept)
|
|
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(const &&noexcept)
|
|
|
|
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile noexcept)
|
|
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile const noexcept)
|
|
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile &noexcept)
|
|
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile const &noexcept)
|
|
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile &&noexcept)
|
|
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile const &&noexcept)
|
|
|
|
// -------------------- placeholder and bind_first ---------------------
|
|
// Inspired by https://gist.github.com/engelmarkus/fc1678adbed1b630584c90219f77eb48
|
|
// A placeholder provides a way to construct something equivalent to a std::placeholders::_N
|
|
// with N as a template argument.
|
|
//
|
|
// Note: As placeholders start at 1, therefore placeholder<0> is NOT a valid placeholder.
|
|
template<int>
|
|
struct placeholder {
|
|
};
|
|
|
|
template<typename Func, typename... Args, std::size_t... Is>
|
|
auto bind_first_helper(std::index_sequence<Is...>, Func &&fun, Args... args)
|
|
{
|
|
return std::bind(std::forward<Func>(fun), std::forward<Args>(args)..., placeholder<Is + 1>{}...);
|
|
}
|
|
|
|
// bind_first binds the first arguments to the callable object (i.e. function) to the values provided by args.
|
|
// The return value is a new function taking get_arity<Func> - sizeof...(Args) many arguments, with the first
|
|
// sizeof...(Args) arguments bound to the values of args.
|
|
// This is different to a call with std::bind(fun, args...), as the callable object created by std::bind would
|
|
// in this case now take zero arguments, whilst bind_first still expects the remaining arguments to be provided
|
|
//
|
|
// For now, providing instances of std::placeholders in Args is not allowed, as the implications of this are
|
|
// unclear if sizeof...(Args) != get_arity<Func>. The enable_if_t makes sure none of the Args value is a placeholder.
|
|
//
|
|
// In the future, we could provide another overload of this function that allows placeholders, as long as all arguments
|
|
// are bound.
|
|
template<
|
|
typename Func,
|
|
typename... Args,
|
|
/*Disallow any placeholder arguments, they would mess with the number and ordering of required and bound arguments, and are, for now, unsupported*/
|
|
typename = std::enable_if_t<std::conjunction_v<std::negation<std::is_placeholder<Args>>...>>>
|
|
auto bind_first(Func &&fun, Args &&...args)
|
|
{
|
|
return bind_first_helper(std::make_index_sequence<get_arity<Func>() - sizeof...(Args)>{}, std::forward<Func>(fun), std::forward<Args>(args)...);
|
|
}
|
|
|
|
} // namespace Private
|
|
|
|
} // namespace KDBindings
|
|
|
|
namespace std {
|
|
|
|
// This allows a placeholder to be used as a replacement of a std::placeholders.
|
|
template<int N>
|
|
struct is_placeholder<KDBindings::Private::placeholder<N>>
|
|
: integral_constant<int, N> {
|
|
};
|
|
|
|
} // namespace std
|