类型擦除类型擦除,`任何`问题?
Type erasing type erasure, `any` questions?
所以,假设我想使用类型擦除来键入擦除。
我可以为启用自然的变体创建伪方法:
pseudo_method print = [](auto&& self, auto&& os){ os << self; };
std::variant<A,B,C> var = // create a variant of type A B or C
(var->*print)(std::cout); // print it out without knowing what it is
我的问题是,如何将其扩展到 std::any
?
无法做到"in the raw"。但是在我们分配 to/construct 一个 std::any
的地方,我们有我们需要的类型信息。
因此,理论上,增强的 any
:
template<class...OperationsToTypeErase>
struct super_any {
std::any data;
// or some transformation of OperationsToTypeErase?
std::tuple<OperationsToTypeErase...> operations;
// ?? what for ctor/assign/etc?
};
可以以某种方式自动重新绑定一些代码,以便上述类型的语法可以工作。
理想情况下,它会像变体案例一样简洁。
template<class...Ops, class Op,
// SFINAE filter that an op matches:
std::enable_if_t< std::disjunction< std::is_same<Ops, Op>... >{}, int>* =nullptr
>
decltype(auto) operator->*( super_any<Ops...>& a, any_method<Op> ) {
return std::get<Op>(a.operations)(a.data);
}
现在我可以将其保持为 类型,同时合理地使用 lambda 语法来保持简单吗?
理想情况下我想要:
any_method<void(std::ostream&)> print =
[](auto&& self, auto&& os){ os << self; };
using printable_any = make_super_any<&print>;
printable_any bob = 7; // sets up the printing data attached to the any
int main() {
(bob->*print)(std::cout); // prints 7
bob = 3.14159;
(bob->*print)(std::cout); // prints 3.14159
}
或类似的语法。这不可能吗?不可行?容易吗?
这是一个使用 C++14 和 boost::any
的解决方案,因为我没有 C++17 编译器。
我们最终得到的语法是:
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);
这几乎是最优的。通过我认为简单的 C++17 更改,它应该看起来像:
constexpr any_method<void(std::ostream&)> print =
[](auto&& p, std::ostream& t){ t << p << "\n"; };
super_any<&print> a = 7;
(a->*print)(std::cout);
在 C++17 中,我会通过使用指向 any_method
的 auto*...
指针而不是 decltype
噪音来改进这一点。
公开继承any
有点冒险,好像有人把最上面的any
拿掉修改了,any_method_data
的tuple
就out了日期。也许我们应该模仿整个 any
界面而不是公开继承。
@dyp 在对 OP 的评论中写了一个概念证明。这是基于他的工作,添加了值语义(从 boost::any
窃取)进行清理。 @cpplearner 基于指针的解决方案用于缩短它(谢谢!),然后我在其上添加了 vtable 优化。
首先我们使用标签来传递类型:
template<class T>struct tag_t{constexpr tag_t(){};};
template<class T>constexpr tag_t<T> tag{};
此特征 class 获取与 any_method
一起存储的签名:
给定 any_method
:
,这将创建一个函数指针类型和一个用于所述函数指针的工厂
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...)>
{
using type = R(*)(boost::any&, any_method const*, Args...);
template<class T>
type operator()( tag_t<T> )const{
return [](boost::any& self, any_method const* method, Args...args) {
return (*method)( boost::any_cast<T&>(self), decltype(args)(args)... );
};
}
};
现在我们不想在 super_any
中为每个操作存储一个函数指针。所以我们将函数指针捆绑到一个 vtable 中:
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 );
}
};
我们可以专门针对 vtable 较小的情况(例如,1 个项目),并在这些情况下使用存储在-class 中的直接指针以提高效率。
现在我们开始 super_any
。我使用 super_any_t
使 super_any
的声明更容易一些。
template<class...methods>
struct super_any_t;
这里搜索SFINAE super any 支持的方法:
template<class super_any, class method>
struct super_method_applies : std::false_type {};
template<class M0, class...Methods, class method>
struct super_method_applies<super_any_t<M0, Methods...>, method> :
std::integral_constant<bool, std::is_same<M0, method>{} || super_method_applies<super_any_t<Methods...>, method>{}>
{};
这是我们全局创建的伪方法指针,如 print
,const
ly。
我们将构造此对象的对象存储在 any_method
中。请注意,如果您使用非 lambda 构造它,事情会变得很棘手,因为此 any_method
的 type 用作调度机制的一部分。
template<class Sig, class F>
struct any_method {
using signature=Sig;
private:
F f;
public:
template<class Any,
// SFINAE testing that one of the Anys's matches this type:
std::enable_if_t< super_method_applies< std::decay_t<Any>, any_method >{}, int>* =nullptr
>
friend auto operator->*( Any&& self, any_method const& m ) {
// we don't use the value of the any_method, because each any_method has
// a unique type (!) and we check that one of the auto*'s in the super_any
// already has a pointer to us. We then dispatch to the corresponding
// any_method_data...
return [&self, invoke = self.get_invoker(tag<any_method>), m](auto&&...args)->decltype(auto)
{
return invoke( decltype(self)(self), &m, decltype(args)(args)... );
};
}
any_method( F fin ):f(std::move(fin)) {}
template<class...Args>
decltype(auto) operator()(Args&&...args)const {
return f(std::forward<Args>(args)...);
}
};
一个工厂方法,我认为在 C++17 中不需要:
template<class Sig, class F>
any_method<Sig, std::decay_t<F>>
make_any_method( F&& f ) {
return {std::forward<F>(f)};
}
这是扩充后的any
。它既是一个 any
,又带有一束类型擦除函数指针,只要所包含的 any
发生变化:
template<class... methods>
struct super_any_t:boost::any, any_methods<methods...> {
private:
template<class T>
T* get() { return boost::any_cast<T*>(this); }
public:
template<class T,
std::enable_if_t< !std::is_same<std::decay_t<T>, super_any_t>{}, int>* =nullptr
>
super_any_t( T&& t ):
boost::any( std::forward<T>(t) )
{
using dT=std::decay_t<T>;
this->change_type( tag<dT> );
}
super_any_t()=default;
super_any_t(super_any_t&&)=default;
super_any_t(super_any_t const&)=default;
super_any_t& operator=(super_any_t&&)=default;
super_any_t& operator=(super_any_t const&)=default;
template<class T,
std::enable_if_t< !std::is_same<std::decay_t<T>, super_any_t>{}, int>* =nullptr
>
super_any_t& operator=( T&& t ) {
((boost::any&)*this) = std::forward<T>(t);
using dT=std::decay_t<T>;
this->change_type( tag<dT> );
return *this;
}
};
因为我们将 any_method
存储为 const
对象,这使得制作 super_any
更容易一些:
template<class...Ts>
using super_any = super_any_t< std::remove_const_t<std::remove_reference_t<Ts>>... >;
测试代码:
const auto print = make_any_method<void(std::ostream&)>([](auto&& p, std::ostream& t){ t << p << "\n"; });
const auto wprint = make_any_method<void(std::wostream&)>([](auto&& p, std::wostream& os ){ os << p << L"\n"; });
const auto wont_work = make_any_method<void(std::ostream&)>([](auto&& p, std::ostream& t){ t << p << "\n"; });
struct X {};
int main()
{
super_any<decltype(print), decltype(wprint)> a = 7;
super_any<decltype(print), decltype(wprint)> a2 = 7;
(a->*print)(std::cout);
(a->*wprint)(std::wcout);
// (a->*wont_work)(std::cout);
double d = 4.2;
a = d;
(a->*print)(std::cout);
(a->*wprint)(std::wcout);
(a2->*print)(std::cout);
(a2->*wprint)(std::wcout);
// a = X{}; // generates an error if you try to store a non-printable
}
当我尝试在 super_any
中存储不可打印的 struct X{};
时出现的错误消息至少在 clang 上似乎是合理的:
main.cpp:150:87: error: invalid operands to binary expression ('std::ostream' (aka 'basic_ostream<char>') and 'X')
const auto x0 = make_any_method<void(std::ostream&)>([](auto&& p, std::ostream& t){ t << p << "\n"; });
当您尝试将 X{}
分配给 super_any<decltype(x0)>
时,就会发生这种情况。
any_method
的结构与 pseudo_method
的结构充分兼容,后者对可能合并的变体的作用相似。
我在这里使用了一个手动 vtable 来将类型擦除开销保持在每个 super_any
1 个指针。这会为每个 any_method 调用增加重定向成本。我们可以很容易地将指针直接存储在 super_any
中,并且不难将其作为 super_any
的参数。无论如何,在 1 擦除方法的情况下,我们应该直接存储它。
两个相同类型的不同 any_method
(例如,都包含一个函数指针)产生相同类型的 super_any
。这会导致查找时出现问题。
区分它们有点棘手。如果我们将 super_any
更改为 auto* any_method
,我们可以将所有相同类型的 any_method
捆绑在 vtable 元组中,然后对匹配指针进行线性搜索(如果有)大于 1。编译器应该优化线性搜索,除非你正在做一些疯狂的事情,比如将引用或指针传递给我们正在使用的特定 any_method
。
然而,这似乎超出了这个答案的范围;存在这种改进就足够了。
此外,可以在左侧添加一个 ->*
,它接受一个指针(甚至是引用!),让它检测到并将其传递给 lambda。这可以使它成为真正的 "any method",因为它适用于变体 super_anys 和使用该方法的指针。
通过一些 if constexpr
的工作,lambda 可以在每种情况下分支执行 ADL 或方法调用。
这应该给我们:
(7->*print)(std::cout);
((super_any<&print>)(7)->*print)(std::cout); // C++17 version of above syntax
((std::variant<int, double>{7})->*print)(std::cout);
int* ptr = new int(7);
(ptr->*print)(std::cout);
(std::make_unique<int>(7)->*print)(std::cout);
(std::make_shared<int>(7)->*print)(std::cout);
与 any_method
只是 "doing the right thing"(将值提供给 std::cout <<
)。
这是我的解决方案。它看起来比 Yakk 的短,而且它没有使用 std::aligned_storage
和 placement new。它还支持有状态和局部仿函数(这意味着它可能永远不可能写成 super_any<&print>
,因为 print
可能是一个局部变量)。
any_method:
template<class F, class Sig> struct any_method;
template<class F, class Ret, class... Args> struct any_method<F,Ret(Args...)> {
F f;
template<class T>
static Ret invoker(any_method& self, boost::any& data, Args... args) {
return self.f(boost::any_cast<T&>(data), std::forward<Args>(args)...);
}
using invoker_type = Ret (any_method&, boost::any&, Args...);
};
make_any_method:
template<class Sig, class F>
any_method<std::decay_t<F>,Sig> make_any_method(F&& f) {
return { std::forward<F>(f) };
}
super_any:
template<class...OperationsToTypeErase>
struct super_any {
boost::any data;
std::tuple<typename OperationsToTypeErase::invoker_type*...> operations = {};
template<class T, class ContainedType = std::decay_t<T>>
super_any(T&& t)
: data(std::forward<T>(t))
, operations((OperationsToTypeErase::template invoker<ContainedType>)...)
{}
template<class T, class ContainedType = std::decay_t<T>>
super_any& operator=(T&& t) {
data = std::forward<T>(t);
operations = { (OperationsToTypeErase::template invoker<ContainedType>)... };
return *this;
}
};
运算符->*:
template<class...Ops, class F, class Sig,
// SFINAE filter that an op matches:
std::enable_if_t< std::disjunction< std::is_same<Ops, any_method<F,Sig>>... >{}, int> = 0
>
auto operator->*( super_any<Ops...>& a, any_method<F,Sig> f) {
auto fptr = std::get<typename any_method<F,Sig>::invoker_type*>(a.operations);
return [fptr,f, &a](auto&&... args) mutable {
return fptr(f, a.data, std::forward<decltype(args)>(args)...);
};
}
用法:
#include <iostream>
auto print = make_any_method<void(std::ostream&)>(
[](auto&& self, auto&& os){ os << self; }
);
using printable_any = super_any<decltype(print)>;
printable_any bob = 7; // sets up the printing data attached to the any
int main() {
(bob->*print)(std::cout); // prints 7
bob = 3.14159;
(bob->*print)(std::cout); // prints 3.14159
}
所以,假设我想使用类型擦除来键入擦除。
我可以为启用自然的变体创建伪方法:
pseudo_method print = [](auto&& self, auto&& os){ os << self; };
std::variant<A,B,C> var = // create a variant of type A B or C
(var->*print)(std::cout); // print it out without knowing what it is
我的问题是,如何将其扩展到 std::any
?
无法做到"in the raw"。但是在我们分配 to/construct 一个 std::any
的地方,我们有我们需要的类型信息。
因此,理论上,增强的 any
:
template<class...OperationsToTypeErase>
struct super_any {
std::any data;
// or some transformation of OperationsToTypeErase?
std::tuple<OperationsToTypeErase...> operations;
// ?? what for ctor/assign/etc?
};
可以以某种方式自动重新绑定一些代码,以便上述类型的语法可以工作。
理想情况下,它会像变体案例一样简洁。
template<class...Ops, class Op,
// SFINAE filter that an op matches:
std::enable_if_t< std::disjunction< std::is_same<Ops, Op>... >{}, int>* =nullptr
>
decltype(auto) operator->*( super_any<Ops...>& a, any_method<Op> ) {
return std::get<Op>(a.operations)(a.data);
}
现在我可以将其保持为 类型,同时合理地使用 lambda 语法来保持简单吗?
理想情况下我想要:
any_method<void(std::ostream&)> print =
[](auto&& self, auto&& os){ os << self; };
using printable_any = make_super_any<&print>;
printable_any bob = 7; // sets up the printing data attached to the any
int main() {
(bob->*print)(std::cout); // prints 7
bob = 3.14159;
(bob->*print)(std::cout); // prints 3.14159
}
或类似的语法。这不可能吗?不可行?容易吗?
这是一个使用 C++14 和 boost::any
的解决方案,因为我没有 C++17 编译器。
我们最终得到的语法是:
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);
这几乎是最优的。通过我认为简单的 C++17 更改,它应该看起来像:
constexpr any_method<void(std::ostream&)> print =
[](auto&& p, std::ostream& t){ t << p << "\n"; };
super_any<&print> a = 7;
(a->*print)(std::cout);
在 C++17 中,我会通过使用指向 any_method
的 auto*...
指针而不是 decltype
噪音来改进这一点。
公开继承any
有点冒险,好像有人把最上面的any
拿掉修改了,any_method_data
的tuple
就out了日期。也许我们应该模仿整个 any
界面而不是公开继承。
@dyp 在对 OP 的评论中写了一个概念证明。这是基于他的工作,添加了值语义(从 boost::any
窃取)进行清理。 @cpplearner 基于指针的解决方案用于缩短它(谢谢!),然后我在其上添加了 vtable 优化。
首先我们使用标签来传递类型:
template<class T>struct tag_t{constexpr tag_t(){};};
template<class T>constexpr tag_t<T> tag{};
此特征 class 获取与 any_method
一起存储的签名:
给定 any_method
:
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...)>
{
using type = R(*)(boost::any&, any_method const*, Args...);
template<class T>
type operator()( tag_t<T> )const{
return [](boost::any& self, any_method const* method, Args...args) {
return (*method)( boost::any_cast<T&>(self), decltype(args)(args)... );
};
}
};
现在我们不想在 super_any
中为每个操作存储一个函数指针。所以我们将函数指针捆绑到一个 vtable 中:
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 );
}
};
我们可以专门针对 vtable 较小的情况(例如,1 个项目),并在这些情况下使用存储在-class 中的直接指针以提高效率。
现在我们开始 super_any
。我使用 super_any_t
使 super_any
的声明更容易一些。
template<class...methods>
struct super_any_t;
这里搜索SFINAE super any 支持的方法:
template<class super_any, class method>
struct super_method_applies : std::false_type {};
template<class M0, class...Methods, class method>
struct super_method_applies<super_any_t<M0, Methods...>, method> :
std::integral_constant<bool, std::is_same<M0, method>{} || super_method_applies<super_any_t<Methods...>, method>{}>
{};
这是我们全局创建的伪方法指针,如 print
,const
ly。
我们将构造此对象的对象存储在 any_method
中。请注意,如果您使用非 lambda 构造它,事情会变得很棘手,因为此 any_method
的 type 用作调度机制的一部分。
template<class Sig, class F>
struct any_method {
using signature=Sig;
private:
F f;
public:
template<class Any,
// SFINAE testing that one of the Anys's matches this type:
std::enable_if_t< super_method_applies< std::decay_t<Any>, any_method >{}, int>* =nullptr
>
friend auto operator->*( Any&& self, any_method const& m ) {
// we don't use the value of the any_method, because each any_method has
// a unique type (!) and we check that one of the auto*'s in the super_any
// already has a pointer to us. We then dispatch to the corresponding
// any_method_data...
return [&self, invoke = self.get_invoker(tag<any_method>), m](auto&&...args)->decltype(auto)
{
return invoke( decltype(self)(self), &m, decltype(args)(args)... );
};
}
any_method( F fin ):f(std::move(fin)) {}
template<class...Args>
decltype(auto) operator()(Args&&...args)const {
return f(std::forward<Args>(args)...);
}
};
一个工厂方法,我认为在 C++17 中不需要:
template<class Sig, class F>
any_method<Sig, std::decay_t<F>>
make_any_method( F&& f ) {
return {std::forward<F>(f)};
}
这是扩充后的any
。它既是一个 any
,又带有一束类型擦除函数指针,只要所包含的 any
发生变化:
template<class... methods>
struct super_any_t:boost::any, any_methods<methods...> {
private:
template<class T>
T* get() { return boost::any_cast<T*>(this); }
public:
template<class T,
std::enable_if_t< !std::is_same<std::decay_t<T>, super_any_t>{}, int>* =nullptr
>
super_any_t( T&& t ):
boost::any( std::forward<T>(t) )
{
using dT=std::decay_t<T>;
this->change_type( tag<dT> );
}
super_any_t()=default;
super_any_t(super_any_t&&)=default;
super_any_t(super_any_t const&)=default;
super_any_t& operator=(super_any_t&&)=default;
super_any_t& operator=(super_any_t const&)=default;
template<class T,
std::enable_if_t< !std::is_same<std::decay_t<T>, super_any_t>{}, int>* =nullptr
>
super_any_t& operator=( T&& t ) {
((boost::any&)*this) = std::forward<T>(t);
using dT=std::decay_t<T>;
this->change_type( tag<dT> );
return *this;
}
};
因为我们将 any_method
存储为 const
对象,这使得制作 super_any
更容易一些:
template<class...Ts>
using super_any = super_any_t< std::remove_const_t<std::remove_reference_t<Ts>>... >;
测试代码:
const auto print = make_any_method<void(std::ostream&)>([](auto&& p, std::ostream& t){ t << p << "\n"; });
const auto wprint = make_any_method<void(std::wostream&)>([](auto&& p, std::wostream& os ){ os << p << L"\n"; });
const auto wont_work = make_any_method<void(std::ostream&)>([](auto&& p, std::ostream& t){ t << p << "\n"; });
struct X {};
int main()
{
super_any<decltype(print), decltype(wprint)> a = 7;
super_any<decltype(print), decltype(wprint)> a2 = 7;
(a->*print)(std::cout);
(a->*wprint)(std::wcout);
// (a->*wont_work)(std::cout);
double d = 4.2;
a = d;
(a->*print)(std::cout);
(a->*wprint)(std::wcout);
(a2->*print)(std::cout);
(a2->*wprint)(std::wcout);
// a = X{}; // generates an error if you try to store a non-printable
}
当我尝试在 super_any
中存储不可打印的 struct X{};
时出现的错误消息至少在 clang 上似乎是合理的:
main.cpp:150:87: error: invalid operands to binary expression ('std::ostream' (aka 'basic_ostream<char>') and 'X') const auto x0 = make_any_method<void(std::ostream&)>([](auto&& p, std::ostream& t){ t << p << "\n"; });
当您尝试将 X{}
分配给 super_any<decltype(x0)>
时,就会发生这种情况。
any_method
的结构与 pseudo_method
的结构充分兼容,后者对可能合并的变体的作用相似。
我在这里使用了一个手动 vtable 来将类型擦除开销保持在每个 super_any
1 个指针。这会为每个 any_method 调用增加重定向成本。我们可以很容易地将指针直接存储在 super_any
中,并且不难将其作为 super_any
的参数。无论如何,在 1 擦除方法的情况下,我们应该直接存储它。
两个相同类型的不同 any_method
(例如,都包含一个函数指针)产生相同类型的 super_any
。这会导致查找时出现问题。
区分它们有点棘手。如果我们将 super_any
更改为 auto* any_method
,我们可以将所有相同类型的 any_method
捆绑在 vtable 元组中,然后对匹配指针进行线性搜索(如果有)大于 1。编译器应该优化线性搜索,除非你正在做一些疯狂的事情,比如将引用或指针传递给我们正在使用的特定 any_method
。
然而,这似乎超出了这个答案的范围;存在这种改进就足够了。
此外,可以在左侧添加一个 ->*
,它接受一个指针(甚至是引用!),让它检测到并将其传递给 lambda。这可以使它成为真正的 "any method",因为它适用于变体 super_anys 和使用该方法的指针。
通过一些 if constexpr
的工作,lambda 可以在每种情况下分支执行 ADL 或方法调用。
这应该给我们:
(7->*print)(std::cout);
((super_any<&print>)(7)->*print)(std::cout); // C++17 version of above syntax
((std::variant<int, double>{7})->*print)(std::cout);
int* ptr = new int(7);
(ptr->*print)(std::cout);
(std::make_unique<int>(7)->*print)(std::cout);
(std::make_shared<int>(7)->*print)(std::cout);
与 any_method
只是 "doing the right thing"(将值提供给 std::cout <<
)。
这是我的解决方案。它看起来比 Yakk 的短,而且它没有使用 std::aligned_storage
和 placement new。它还支持有状态和局部仿函数(这意味着它可能永远不可能写成 super_any<&print>
,因为 print
可能是一个局部变量)。
any_method:
template<class F, class Sig> struct any_method;
template<class F, class Ret, class... Args> struct any_method<F,Ret(Args...)> {
F f;
template<class T>
static Ret invoker(any_method& self, boost::any& data, Args... args) {
return self.f(boost::any_cast<T&>(data), std::forward<Args>(args)...);
}
using invoker_type = Ret (any_method&, boost::any&, Args...);
};
make_any_method:
template<class Sig, class F>
any_method<std::decay_t<F>,Sig> make_any_method(F&& f) {
return { std::forward<F>(f) };
}
super_any:
template<class...OperationsToTypeErase>
struct super_any {
boost::any data;
std::tuple<typename OperationsToTypeErase::invoker_type*...> operations = {};
template<class T, class ContainedType = std::decay_t<T>>
super_any(T&& t)
: data(std::forward<T>(t))
, operations((OperationsToTypeErase::template invoker<ContainedType>)...)
{}
template<class T, class ContainedType = std::decay_t<T>>
super_any& operator=(T&& t) {
data = std::forward<T>(t);
operations = { (OperationsToTypeErase::template invoker<ContainedType>)... };
return *this;
}
};
运算符->*:
template<class...Ops, class F, class Sig,
// SFINAE filter that an op matches:
std::enable_if_t< std::disjunction< std::is_same<Ops, any_method<F,Sig>>... >{}, int> = 0
>
auto operator->*( super_any<Ops...>& a, any_method<F,Sig> f) {
auto fptr = std::get<typename any_method<F,Sig>::invoker_type*>(a.operations);
return [fptr,f, &a](auto&&... args) mutable {
return fptr(f, a.data, std::forward<decltype(args)>(args)...);
};
}
用法:
#include <iostream>
auto print = make_any_method<void(std::ostream&)>(
[](auto&& self, auto&& os){ os << self; }
);
using printable_any = super_any<decltype(print)>;
printable_any bob = 7; // sets up the printing data attached to the any
int main() {
(bob->*print)(std::cout); // prints 7
bob = 3.14159;
(bob->*print)(std::cout); // prints 3.14159
}