From 24eff3ab6d52a3ea96314ab758fe60e08605e8cf Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Wed, 1 May 2019 23:32:16 -0500 Subject: [PATCH] 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. --- include/fc/api.hpp | 70 ++++++++++++++++++++++++++++--- include/fc/rpc/api_connection.hpp | 20 ++++++--- 2 files changed, 78 insertions(+), 12 deletions(-) diff --git a/include/fc/api.hpp b/include/fc/api.hpp index 3f68750..595b5dd 100644 --- a/include/fc/api.hpp +++ b/include/fc/api.hpp @@ -3,6 +3,10 @@ #include #include #include +#include +#include +#include +#include // ms visual c++ (as of 2013) doesn't accept the standard syntax for calling a // templated member function (foo->template bar();) @@ -13,13 +17,67 @@ #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 struct is_optional : public std::false_type {}; + template struct is_optional> : public std::true_type {}; + /// This metafunction determines whether all of its template arguments are instantiations of fc::optional + template struct all_optionals; + template<> struct all_optionals<> : public std::true_type {}; + template struct all_optionals : public std::false_type {}; + template struct all_optionals, Ts...> : public all_optionals {}; + + /// 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, fc::optional), + /// 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, fc::optional), only the last argument can be omitted. + template + struct optionals_callable : public std::function { + using std::function::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 + std::enable_if_t operator()(Args... args) { + auto arguments = hana::make_tuple(std::forward(args)...); + // Get the parameter types corresponding to the omitted arguments + auto optional_types = hana::take_back(hana::tuple_t, + hana::size_c); + // 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; + static_assert(is_optional::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(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 +// static std::function functor( P&& p, R (C::*mem_func)(Args...) ); +// template +// static std::function 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 - static std::function functor( P&& p, R (C::*mem_func)(Args...) ); + static detail::optionals_callable functor( P&& p, R (C::*mem_func)(Args...) ); template - static std::function functor( P&& p, R (C::*mem_func)(Args...)const ); + static detail::optionals_callable functor( P&& p, R (C::*mem_func)(Args...)const ); }; - + template< typename Interface, typename Transform > struct vtable : public std::enable_shared_from_this> { private: vtable(); }; @@ -57,13 +115,13 @@ namespace fc { // defined in api_connection.hpp template< typename T > - api as(); + api as(); }; typedef std::shared_ptr< api_base > api_ptr; class api_connection; - template + template class api : public api_base { public: typedef vtable vtable_type; diff --git a/include/fc/rpc/api_connection.hpp b/include/fc/rpc/api_connection.hpp index 055f6dd..8e1df24 100644 --- a/include/fc/rpc/api_connection.hpp +++ b/include/fc/rpc/api_connection.hpp @@ -33,7 +33,9 @@ namespace fc { template std::function bind_first_arg( const std::function& f, Arg0 a0 ) { - return [=]( Args... args ) { return f( a0, args... ); }; + // Capture a0 this way because of a {compiler,fc,???} bug that causes optional() to be incorrectly + // captured as optional(false). + return [f, a0 = std::decay_t(a0)]( Args... args ) { return f( a0, args... ); }; } template R call_generic( const std::function& f, variants::const_iterator a0, variants::const_iterator e, uint32_t max_depth = 1 ) @@ -44,9 +46,11 @@ namespace fc { template R call_generic( const std::function& 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...>::value; + FC_ASSERT( a0 != e || optional_args ); FC_ASSERT( max_depth > 0, "Recursion depth exceeded!" ); - return call_generic( bind_first_arg( f, a0->as< typename std::decay::type >( max_depth - 1 ) ), a0+1, e, max_depth - 1 ); + auto arg = (a0 == e)? std::decay_t() : a0->as>(max_depth - 1); + return call_generic( bind_first_arg( f, arg ), a0+1, e, max_depth - 1 ); } template @@ -137,7 +141,9 @@ namespace fc { template std::function bind_first_arg( const std::function& f, Arg0 a0 )const { - return [=]( Args... args ) { return f( a0, args... ); }; + // Capture a0 this way because of a {compiler,fc,???} bug that causes optional() to be incorrectly + // captured as optional(false). + return [f, a0 = std::decay_t(a0)]( Args... args ) { return f( a0, args... ); }; } template @@ -172,9 +178,11 @@ namespace fc { template R call_generic( const std::function& 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...>::value; + FC_ASSERT( a0 != e || optional_args, "too few arguments passed to method" ); FC_ASSERT( max_depth > 0, "Recursion depth exceeded!" ); - return call_generic( this->bind_first_arg( f, a0->as< typename std::decay::type >( max_depth - 1 ) ), a0+1, e, max_depth - 1 ); + auto arg = (a0 == e)? std::decay_t() : a0->as>(max_depth - 1); + return call_generic( this->bind_first_arg( f, arg ), a0+1, e, max_depth - 1 ); } struct api_visitor