boost::any
私は C++17 コンパイラを持っていないので、これは C++14 と を使用するソリューションです。
最終的な構文は次のとおりです。
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では、ノイズの代わりにauto*...
ポインタを取得することでこれを改善します。any_method
decltype
from を公に継承するany
のは少し危険です。誰かがany
トップを外して変更すると、tuple
ofany_method_data
が古くなってしまうからです。any
おそらく、公に継承するのではなく、インターフェイス全体を模倣する必要があります。
@dyp は、OP へのコメントに概念実証を書きました。これは彼の作品に基づいており、値のセマンティクス (から盗まれたboost::any
) が追加されてクリーンアップされています。@cplearner のポインターベースのソリューションを使用して短縮し (ありがとう!)、その上に vtable の最適化を追加しました。
まず、タグを使用して型を渡します。
template<class T>struct tag_t{constexpr tag_t(){};};
template<class T>constexpr tag_t<T> tag{};
この特性クラスは、次のように保存された署名を取得します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 項目) に特化し、そのような場合に効率のためにクラス内に格納された直接ポインターを使用することができます。
を開始しsuper_any
ます。super_any_t
の宣言をsuper_any
少し簡単にするために使用します。
template<class...methods>
struct super_any_t;
これは、super any が SFINAE でサポートするメソッドを検索します。
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 で作成します。
this を構築するオブジェクトを .xml 内に保存しますany_method
。thisの型any_method
はディスパッチ メカニズムの一部として使用されるため、非ラムダで構築すると複雑になる可能性があることに注意してください。
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
s を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
}
実例。
struct X{};
印刷できないものを内部に保存しようとしたときのエラーメッセージはsuper_any
、少なくとも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 を使用して、型消去のオーバーヘッドを 1 ポインターあたり 1 に維持しましたsuper_any
。これにより、すべての any_method 呼び出しにリダイレクト コストが追加されます。ポインタを に直接格納するのはsuper_any
非常に簡単で、それを のパラメータにするのも難しくありませんsuper_any
。いずれにせよ、1 消去されたメソッドの場合は、直接保存する必要があります。
同じ型の2 つの異なるany_method
(たとえば、どちらも関数ポインターを含む) は、同じ種類の を生成しますsuper_any
。これにより、ルックアップ時に問題が発生します。
それらを区別するのは少し難しいです。super_any
を takeに変更した場合auto* any_method
、すべての同一型any_method
を vtable タプルにまとめてから、一致するポインターが複数ある場合は線形検索を行うことができます。あなたは、私たちが使用している特定のものへの参照またはポインターを渡すなど、何かクレイジーなことをしていますany_method
。
ただし、それはこの回答の範囲を超えているようです。その改善の存在は今のところ十分です。
さらに->*
、左側にポインター (または参照も!) を取る a を追加して、これを検出し、ラムダにも渡すことができます。これにより、そのメソッドを使用してバリアント、super_anys、およびポインターで機能するという点で、真の「任意のメソッド」にすることができます。
ちょっとしたif constexpr
作業で、ラムダはあらゆる場合に 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
「正しいことをする」だけで(値をに供給しています)std::cout <<
。