This example uses C++14 and boost::any
. In C++17 you can swap in std::any
instead.
The syntax we end up with is:
const auto print =
make_any_method<void(std::ostream&)>([](auto&& p, std::ostream& t){ t << p << "\\n"; });
super_any<decltype(print)> a = 7;
(a->*print)(std::cout);
which is almost optimal.
This example is based off of work by @dyp and @cpplearner as well as my own.
First we use a tag to pass around types:
template<class T>struct tag_t{constexpr tag_t(){};};
template<class T>constexpr tag_t<T> tag{};
This trait class gets the signature stored with an any_method
:
This creates a function pointer type, and a factory for said function pointers, given an any_method
:
template<class any_method>
using any_sig_from_method = typename any_method::signature;
template<class any_method, class Sig=any_sig_from_method<any_method>>
struct any_method_function;
template<class any_method, class R, class...Args>
struct any_method_function<any_method, R(Args...)>
{
template<class T>
using decorate = std::conditional_t< any_method::is_const, T const, T >;
using any = decorate<boost::any>;
using type = R(*)(any&, any_method const*, Args&&...);
template<class T>
type operator()( tag_t<T> )const{
return +[](any& self, any_method const* method, Args&&...args) {
return (*method)( boost::any_cast<decorate<T>&>(self), decltype(args)(args)... );
};
}
};
any_method_function::type
is the type of a function pointer we will store alongside the instance. any_method_function::operator()
takes a tag_t<T>
and writes a custom instance of the any_method_function::type
that assumes the any&
is going to be a T
.
We want to be able to type-erase more than one method at a time. So we bundle them up in a tuple, and write a helper wrapper to stick the tuple into static storage on a per-type basis and maintain a pointer to them.
template<class...any_methods>
using any_method_tuple = std::tuple< typename any_method_function<any_methods>::type... >;
template<class...any_methods, class T>
any_method_tuple<any_methods...> make_vtable( tag_t<T> ) {
return std::make_tuple(
any_method_function<any_methods>{}(tag<T>)...
);
}
template<class...methods>
struct any_methods {
private:
any_method_tuple<methods...> const* vtable = 0;
template<class T>
static any_method_tuple<methods...> const* get_vtable( tag_t<T> ) {
static const auto table = make_vtable<methods...>(tag<T>);
return &table;
}
public:
any_methods() = default;
template<class T>
any_methods( tag_t<T> ): vtable(get_vtable(tag<T>)) {}
any_methods& operator=(any_methods const&)=default;
template<class T>
void change_type( tag_t<T> ={} ) { vtable = get_vtable(tag<T>); }
template<class any_method>
auto get_invoker( tag_t<any_method> ={} ) const {
return std::get<typename any_method_function<any_method>::type>( *vtable );
}
};
We could specialize this for a cases where the vtable is small (for example, 1 item), and use direct pointers stored in-class in those cases for efficiency.
Now we start the super_any
. I use super_any_t
to make the declaration of super_any
a bit easier.
template<class...methods>
struct super_any_t;
This searches the methods that the super any supports for SFINAE and better error messages:
template<class super_any, class method>
struct super_method_applies_helper : std::false_type {};
template<class M0, class...Methods, class method>
struct super_method_applies_helper<super_any_t<M0, Methods...>, method> :
std::integral_constant<bool, std::is_same<M0, method>{} || super_method_applies_helper<super_any_t<Methods...>, method>{}>
{};
template<class...methods, class method>
auto super_method_test( super_any_t<methods...> const&, tag_t<method> )
{
return std::integral_constant<bool, super_method_applies_helper< super_any_t<methods...>, method >{} && method::is_const >{};
}
template<class...methods, class method>
auto super_method_test( super_any_t<methods...>&, tag_t<method> )
{
return std::integral_constant<bool, super_method_applies_helper< super_any_t<methods...>, method >{} >{};
}
template<class super_any, class method>
struct super_method_applies:
decltype( super_method_test( std::declval<super_any>(), tag<method> ) )
{};
Next we create the any_method
type. An any_method
is a pseudo-method-pointer. We create it globally and const
ly using syntax like: