24

では、type eraser を使用して type erase を実行したいとします。

自然を可能にするバリアントの疑似メソッドを作成できます。

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ですか?

「そのまま」ではできません。しかし、a に代入/構築する時点で、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);
}

これをtypeに保ちながら、ラムダ構文を合理的に使用して物事を単純にすることはできますか?

理想的には私が欲しい:

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
}

または同様の構文。これは不可能ですか?実行不可能ですか?簡単?

4

2 に答える 2

11

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_methoddecltype

from を公に継承するanyのは少し危険です。誰かがanyトップを外して変更すると、tupleofany_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グローバルかつconstly で作成します。

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_methods を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 <<

于 2016-08-08T20:09:24.217 に答える
8

これが私の解決策です。Yakkのものよりも短く見え、使用std::aligned_storageも配置も新しくありません。さらに、ステートフル ファンクターとローカル ファンクターをサポートします (これは、ローカル変数である可能性があるためsuper_any<&print>、を書き込むことができない可能性があることを意味します)。print

任意の方法:

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
}

ライブ

于 2016-08-10T05:54:50.283 に答える