Add API support for optional arguments
FC_API now supports optional arguments! Any trailing arguments of an fc::optional type are now optional, and do not need to be supplied. If omitted, they will be inferred to be null optionals.
This commit is contained in:
parent
7e612bee5d
commit
24eff3ab6d
2 changed files with 78 additions and 12 deletions
|
|
@ -3,6 +3,10 @@
|
|||
#include <functional>
|
||||
#include <boost/any.hpp>
|
||||
#include <boost/config.hpp>
|
||||
#include <boost/hana/tuple.hpp>
|
||||
#include <boost/hana/take_back.hpp>
|
||||
#include <boost/hana/transform.hpp>
|
||||
#include <boost/hana/concat.hpp>
|
||||
|
||||
// ms visual c++ (as of 2013) doesn't accept the standard syntax for calling a
|
||||
// templated member function (foo->template bar();)
|
||||
|
|
@ -13,11 +17,65 @@
|
|||
#endif
|
||||
|
||||
namespace fc {
|
||||
struct identity_member {
|
||||
namespace detail {
|
||||
namespace hana = boost::hana;
|
||||
|
||||
/// This metafunction determines whether its template argument is an instantiation of fc::optional
|
||||
template<typename T> struct is_optional : public std::false_type {};
|
||||
template<typename T> struct is_optional<fc::optional<T>> : public std::true_type {};
|
||||
/// This metafunction determines whether all of its template arguments are instantiations of fc::optional
|
||||
template<typename... Ts> struct all_optionals;
|
||||
template<> struct all_optionals<> : public std::true_type {};
|
||||
template<typename T, typename... Ts> struct all_optionals<T, Ts...> : public std::false_type {};
|
||||
template<typename T, typename... Ts> struct all_optionals<fc::optional<T>, Ts...> : public all_optionals<Ts...> {};
|
||||
|
||||
/// A wrapper of std::function allowing callers to omit the last several arguments if those arguments are
|
||||
/// fc::optional types. i.e. given a function taking (int, double, bool, fc::optional<string>, fc::optional<char>),
|
||||
/// whereas normally the last two arguments must be provided, this template allows them to be omitted.
|
||||
/// Note that this only applies to trailing optional arguments, i.e. given a callable taking
|
||||
/// (fc::optional<int>, int, fc::optional<int>), only the last argument can be omitted.
|
||||
template<typename R, typename... Parameters>
|
||||
struct optionals_callable : public std::function<R(Parameters...)> {
|
||||
using std::function<R(Parameters...)>::operator();
|
||||
|
||||
/// Overload the function call operator, enabled if the caller provides fewer arguments than there are parameters.
|
||||
/// Pads out the provided arguments with default-constructed optionals, checking that they are indeed optional types
|
||||
template<class... Args>
|
||||
std::enable_if_t<sizeof...(Args) < sizeof...(Parameters), R> operator()(Args... args) {
|
||||
auto arguments = hana::make_tuple(std::forward<Args>(args)...);
|
||||
// Get the parameter types corresponding to the omitted arguments
|
||||
auto optional_types = hana::take_back(hana::tuple_t<Parameters...>,
|
||||
hana::size_c<sizeof...(Parameters) - sizeof...(Args)>);
|
||||
// Transform the types into default-constructed values, checking that they are optional types
|
||||
auto optional_values = hana::transform(optional_types, [](auto hanatype) {
|
||||
using type = std::decay_t<typename decltype(hanatype)::type>;
|
||||
static_assert(is_optional<type>::value,
|
||||
"All omitted arguments must correspond to optional parameters.");
|
||||
return type();
|
||||
});
|
||||
auto padded_arguments = hana::concat(arguments, optional_values);
|
||||
return hana::unpack(padded_arguments,
|
||||
[this](auto... params) { return (*this)(std::forward<decltype(params)>(params)...); });
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// This is no longer used and probably no longer can be used without generalizing the infrastructure around it, but I
|
||||
// kept it because it is informative.
|
||||
// struct identity_member {
|
||||
// template<typename R, typename C, typename P, typename... Args>
|
||||
// static std::function<R(Args...)> functor( P&& p, R (C::*mem_func)(Args...) );
|
||||
// template<typename R, typename C, typename P, typename... Args>
|
||||
// static std::function<R(Args...)> functor( P&& p, R (C::*mem_func)(Args...)const );
|
||||
// };
|
||||
/// Used as the Transform template parameter for APIs, this type has two main purposes: first, it reads the argument
|
||||
/// list and return type of a method into template parameters; and second, it uses those types in conjunction with the
|
||||
/// optionals_callable template above to create a function pointer which supports optional arguments.
|
||||
struct identity_member_with_optionals {
|
||||
template<typename R, typename C, typename P, typename... Args>
|
||||
static std::function<R(Args...)> functor( P&& p, R (C::*mem_func)(Args...) );
|
||||
static detail::optionals_callable<R, Args...> functor( P&& p, R (C::*mem_func)(Args...) );
|
||||
template<typename R, typename C, typename P, typename... Args>
|
||||
static std::function<R(Args...)> functor( P&& p, R (C::*mem_func)(Args...)const );
|
||||
static detail::optionals_callable<R, Args...> functor( P&& p, R (C::*mem_func)(Args...)const );
|
||||
};
|
||||
|
||||
template< typename Interface, typename Transform >
|
||||
|
|
@ -57,13 +115,13 @@ namespace fc {
|
|||
|
||||
// defined in api_connection.hpp
|
||||
template< typename T >
|
||||
api<T, identity_member> as();
|
||||
api<T, identity_member_with_optionals> as();
|
||||
};
|
||||
typedef std::shared_ptr< api_base > api_ptr;
|
||||
|
||||
class api_connection;
|
||||
|
||||
template<typename Interface, typename Transform = identity_member >
|
||||
template<typename Interface, typename Transform = identity_member_with_optionals >
|
||||
class api : public api_base {
|
||||
public:
|
||||
typedef vtable<Interface,Transform> vtable_type;
|
||||
|
|
|
|||
|
|
@ -33,7 +33,9 @@ namespace fc {
|
|||
template<typename R, typename Arg0, typename ... Args>
|
||||
std::function<R(Args...)> bind_first_arg( const std::function<R(Arg0,Args...)>& f, Arg0 a0 )
|
||||
{
|
||||
return [=]( Args... args ) { return f( a0, args... ); };
|
||||
// Capture a0 this way because of a {compiler,fc,???} bug that causes optional<bool>() to be incorrectly
|
||||
// captured as optional<bool>(false).
|
||||
return [f, a0 = std::decay_t<Arg0>(a0)]( Args... args ) { return f( a0, args... ); };
|
||||
}
|
||||
template<typename R>
|
||||
R call_generic( const std::function<R()>& f, variants::const_iterator a0, variants::const_iterator e, uint32_t max_depth = 1 )
|
||||
|
|
@ -44,9 +46,11 @@ namespace fc {
|
|||
template<typename R, typename Arg0, typename ... Args>
|
||||
R call_generic( const std::function<R(Arg0,Args...)>& f, variants::const_iterator a0, variants::const_iterator e, uint32_t max_depth )
|
||||
{
|
||||
FC_ASSERT( a0 != e );
|
||||
bool optional_args = all_optionals<std::decay_t<Arg0>, std::decay_t<Args>...>::value;
|
||||
FC_ASSERT( a0 != e || optional_args );
|
||||
FC_ASSERT( max_depth > 0, "Recursion depth exceeded!" );
|
||||
return call_generic<R,Args...>( bind_first_arg<R,Arg0,Args...>( f, a0->as< typename std::decay<Arg0>::type >( max_depth - 1 ) ), a0+1, e, max_depth - 1 );
|
||||
auto arg = (a0 == e)? std::decay_t<Arg0>() : a0->as<std::decay_t<Arg0>>(max_depth - 1);
|
||||
return call_generic<R,Args...>( bind_first_arg<R,Arg0,Args...>( f, arg ), a0+1, e, max_depth - 1 );
|
||||
}
|
||||
|
||||
template<typename R, typename ... Args>
|
||||
|
|
@ -137,7 +141,9 @@ namespace fc {
|
|||
template<typename R, typename Arg0, typename ... Args>
|
||||
std::function<R(Args...)> bind_first_arg( const std::function<R(Arg0,Args...)>& f, Arg0 a0 )const
|
||||
{
|
||||
return [=]( Args... args ) { return f( a0, args... ); };
|
||||
// Capture a0 this way because of a {compiler,fc,???} bug that causes optional<bool>() to be incorrectly
|
||||
// captured as optional<bool>(false).
|
||||
return [f, a0 = std::decay_t<Arg0>(a0)]( Args... args ) { return f( a0, args... ); };
|
||||
}
|
||||
|
||||
template<typename R>
|
||||
|
|
@ -172,9 +178,11 @@ namespace fc {
|
|||
template<typename R, typename Arg0, typename ... Args>
|
||||
R call_generic( const std::function<R(Arg0,Args...)>& f, variants::const_iterator a0, variants::const_iterator e, uint32_t max_depth )
|
||||
{
|
||||
FC_ASSERT( a0 != e, "too few arguments passed to method" );
|
||||
bool optional_args = detail::all_optionals<std::decay_t<Arg0>, std::decay_t<Args>...>::value;
|
||||
FC_ASSERT( a0 != e || optional_args, "too few arguments passed to method" );
|
||||
FC_ASSERT( max_depth > 0, "Recursion depth exceeded!" );
|
||||
return call_generic<R,Args...>( this->bind_first_arg<R,Arg0,Args...>( f, a0->as< typename std::decay<Arg0>::type >( max_depth - 1 ) ), a0+1, e, max_depth - 1 );
|
||||
auto arg = (a0 == e)? std::decay_t<Arg0>() : a0->as<std::decay_t<Arg0>>(max_depth - 1);
|
||||
return call_generic<R,Args...>( this->bind_first_arg<R,Arg0,Args...>( f, arg ), a0+1, e, max_depth - 1 );
|
||||
}
|
||||
|
||||
struct api_visitor
|
||||
|
|
|
|||
Loading…
Reference in a new issue