格納された引数を関数に渡す

私の友人の一人が私に興味深い問題を投げかけました。ポインタを介して関数を呼び出し、以前に保存された引数を渡す必要があります。 前提条件は、std ::関数を使用しないことでした。 この問題に対する私の解決策を共有したいと思います。 厳密に与えられた実装を判断しないでください。 決して完全で包括的なふりをしません。 私はすべてを可能な限りシンプルに、最小限に、しかし十分にしたかったのです。 さらに、2つのソリューションがあります。 私の意見では、そのうちの1つは他のよりも優れています。



最初の解決策は、C ++が既に変数をキャプチャするメカニズムを提供しているという事実に基づいています。 ラムダについてです。 当然、最も明白で最も簡単なのは、このような素晴らしいメカニズムを使用することです。 C ++ 14以上に詳しくない人のために、対応するコードを提供します。



auto Variable = 1; auto Lambda = [Variable]() { someFunction(Variable); };
      
      





このコードでは、変数と呼ばれる変数をキャプチャするラムダ関数が作成されます。 関数lambdaオブジェクト自体は、Lambdaという名前の変数にコピーされます。 この変数を介して、将来ラムダ関数自体を呼び出すことができるようになります。 そして、そのような呼び出しは通常の関数の呼び出しのように見えます:



 Lambda();
      
      





タスクはすでに解決されているように見えますが、実際にはそうではありません。 ラムダ関数は、関数、メソッド、または他のラムダから返すことができますが、後でテンプレートを使用せずにどこかに転送することは困難です。



 auto makeLambda(int Variable) { return [Variable]() { someFunction(Variable); }; } auto Lambda = makeLambda(3); //     ,   ? someOtherFunction(Lambda);
      
      





Lambda関数は、匿名型のオブジェクトであり、コンパイラーのみが知っている内部構造を持っています。 また、純粋なC ++(ライブラリのない言語を意味します)は、プログラマにラムダに対するそれほど多くの操作を提供しません。







原則として、これらの基本的な操作で十分です。これらの操作と言語の他のメカニズムを使用すると、多くのことができるからです。 それが最終的に得たものです。



 #include <utility> #include <cstdint> #include <vector> template <typename Function> class SignalTraits; template <typename R, typename... A> class SignalTraits<R(A...)> { public: using Result = R; }; template <typename Function> class Signal { public: using Result = typename SignalTraits<Function>::Result; template <typename Callable> Signal(Callable Fn) : Storage(sizeof(Fn)) { new (Storage.data()) Callable(std::move(Fn)); Trampoline = [](Signal *S) -> Result { auto CB = static_cast<Callable *>(static_cast<void *>(S->Storage.data())); return (*CB)(); }; } Result invoke() { return Trampoline(this); } private: Result (*Trampoline)(Signal *Self); std::vector<std::uint8_t> Storage; };
      
      





この例では、テンプレートコンストラクターのおかげで、このコンストラクター内で作成されたラムダにはCallable型に関する情報が含まれます。つまり、Storageのデータを目的の型にキャストできます。 実際、これがすべてのトリックです。 変数をキャプチャし、関数とラムダを呼び出すという困難な作業はすべて、コンパイラの責任です。 私の意見では、そのようなソリューションは非常にシンプルでエレガントです。



2番目のソリューションについては、あまり好きではありません。 これには、多くの自己記述コードが含まれており、コンパイラーが既に決定した内容を本質的に解決します。 つまり、変数のキャプチャ。 長い議論や議論はしませんが、すぐにソリューション全体のコードを提供します。 なぜなら それは非常に大きくて気になりません、私は猫の下に隠します:



美しいコードではありません。
 #include <cstdarg> #include <cstdint> #include <vector> template <typename T> struct PromotedTraits { using Type = T; }; template <> struct PromotedTraits<char> { using Type = int; }; template <> struct PromotedTraits<unsigned char> { using Type = unsigned; }; template <> struct PromotedTraits<short> { using Type = int; }; template <> struct PromotedTraits<unsigned short> { using Type = unsigned; }; template <> struct PromotedTraits<float> { using Type = double; }; template <typename... Arguments> class StorageHelper; template <typename T, typename... Arguments> class StorageHelper<T, Arguments...> { public: static void store(va_list &List, std::vector<std::uint8_t> &Storage) { using Type = typename PromotedTraits<T>::Type; union { T Value; std::uint8_t Bytes[sizeof(void *)]; }; Value = va_arg(List, Type); for (auto B : Bytes) { Storage.push_back(B); } StorageHelper<Arguments...>::store(List, Storage); } }; template <> class StorageHelper<> { public: static void store(...) {} }; template <bool, typename...> class InvokeHelper; template <typename... Arguments> class InvokeHelper<true, Arguments...> { public: template <typename Result> static Result invoke(Result (*Fn)(Arguments...), Arguments... Args) { return Fn(Args...); } }; template <typename... Arguments> class InvokeHelper<false, Arguments...> { public: template <typename Result> static Result invoke(...) { return {}; } }; struct Dummy; template <std::size_t Index, typename... Types> class TypeAt { public: using Type = Dummy *; }; template <std::size_t Index, typename T, typename... Types> class TypeAt<Index, T, Types...> { public: using Type = typename TypeAt<(Index - 1u), Types...>::Type; }; template <typename T, typename... Types> class TypeAt<0u, T, Types...> { public: using Type = T; }; template <typename Function> class Signal; template <typename Result, typename... Arguments> class Signal<Result(Arguments...)> { public: using CFunction = Result(Arguments...); Signal(CFunction *Delegate, Arguments... Values) : Delegate(Delegate) { initialize(Delegate, Values...); } Result invoke() { std::uintptr_t *Args = reinterpret_cast<std::uintptr_t *>(Storage.data()); Result R = {}; using T0 = typename TypeAt<0u, Arguments...>::Type; using T1 = typename TypeAt<0u, Arguments...>::Type; // ... and so on. switch (sizeof...(Arguments)) { case 0u: return InvokeHelper<(0u == sizeof...(Arguments)), Arguments...>::template invoke<Result>(Delegate); case 1u: return InvokeHelper<(1u == sizeof...(Arguments)), Arguments...>::template invoke<Result>(Delegate, (T0 &)Args[0]); case 2u: return InvokeHelper<(2u == sizeof...(Arguments)), Arguments...>::template invoke<Result>(Delegate, (T0 &)Args[0], (T1 &)Args[1]); // ... and so on. } return R; } private: void initialize(CFunction *Delegate, ...) { va_list List; va_start(List, Delegate); StorageHelper<Arguments...>::store(List, Storage); va_end(List); } CFunction *Delegate; std::vector<std::uint8_t> Storage; };
      
      





ここで、私の意見では、すべての興味深い点はStorageHelperとInvokeHelperの2つの補助クラスにあります。 最初の例では、省略記号と再帰的なパスを組み合わせて、引数ストアにデータを入力するための型のリストを作成します。 2番目は、このストアから引数を取得するタイプセーフな方法を提供します。 さらに、別の小さなトリックがあります:省略記号は、あるタイプを別のタイプにプロモートします。 つまり ...を通過したフロートは、double、charからint、shortからintなどにキャストされます。



上記のすべての上にこれを要約したいと思います。 私の意見では、両方のソリューションは完璧ではありません。彼らは多くを知らず、車輪を発明しようとしています。 引数を正しくキャプチャして特定の関数に渡す方法を聞かれたら、ためらうことなくstd :: function + lambdaを使用する必要があると思います。 心のための運動として、手元のタスクは非常に良いですが。



あなたが読んだすべてのものが役に立つことを願っています。 これまで読んでくれてありがとう!



All Articles