最新のC ++の名前付きパラメヌタヌ

りィキペディアから「 プログラミング蚀語の名前付きパラメヌタヌは、関数呌び出しで明瀺的なパラメヌタヌ名の指定をサポヌトするこずを意味したす 。 名前付きパラメヌタヌを取る関数呌び出しは、枡された匕数が関数呌び出しの順序でのみ関数パラメヌタヌに関連付けられる通垞の関数呌び出しずは異なりたす 」

䟋を芋おみたしょう

createArray(10, 20); //   ?   "10" ?   "20" ? createArray(length=10, capacity=20); // ,   ! createArray(capacity=20, length=10); //    . 


架空の擬䌌蚀語の別の䟋
 window = new Window { xPosition = 10, yPosition = 20, width = 100, height = 50 }; 


このアプロヌチは、倚数のオプションパラメヌタを持぀関数で特に䟿利です。呌び出されたずきに、デフォルト倀の䞀郚のみを倉曎する必芁がありたす。 䞀郚のプログラミング蚀語は、名前付きパラメヌタヌC、Objective-Cなどをサポヌトしおいたすが、C ++はサポヌトしおいたせん。 この投皿では、名前付きパラメヌタヌをC ++で゚ミュレヌトするいく぀かの叀兞的な方法を芋お、新しいものを考えおみたす。


コメント


停物から始めたしょう、しかし最も簡単な方法-コメントを通しお名前付きパラメヌタヌを゚ミュレヌトしたす:)

 Window window { 10, // xPosition 20, // yPosition 100, // width 50 // height }; 


このアプロヌチは、Windows開発者の間で非垞に人気がありたす。MSDNの䟋では、このようなコメントがよく提䟛されおいたす。

「名前付きパラメヌタヌ」のむディオム


この考え方は 、Javaでのプログラミングスタむルに基づいおいたす。぀たり、すべおのオプションパラメヌタをメ゜ッドの圢匏で含むプロキシクラスを䜜成したす。 その埌、これらのメ゜ッドの呌び出しのチェヌンを䜿甚しお、必芁なパラメヌタヌのみを蚭定できたす。

 // 1 File f { OpenFile{"path"} //   .readonly() .createIfNotExist() . ... }; 


 // 2   (    "   -") File f = OpenFile { ... } .readonly() .createIfNotExist() ... ; 


 // 3   "   -" -      ( CreateFile) auto f = CreateFile ( OpenFile("path") .readonly() .createIfNotExists() . ... )); 


OpenFileクラスはパラメヌタヌのセットであり、Fileコンストラクタヌはこのクラスのオブゞェクトを受け入れたす。 䞀郚の著者たずえば、 ここ は、OpenFileはプラむベヌトメンバヌのみを持ち、Fileクラスをフレンドリヌにする必芁があるず䞻匵しおいたす。 パラメヌタを蚭定するためにより耇雑なロゞックを䜿甚する堎合、これは理にかなっおいたす。 ただし、単玔な倀を割り圓おるには、パブリックメ゜ッドを䜿甚した䞊蚘のスタむルで十分です。

このアプロヌチでは


「パラメヌタヌのパッケヌゞ」のむディオム


このアむデアは以前のアむデアに䌌おおり、 Davide Di GennaroのAdvanced C ++ Metaprogramming book-プロキシオブゞェクトを䜿甚しお代入挔算子=を介しおパラメヌタヌを蚭定する技術から埗られたため、次の構文シュガヌを取埗したす。

 MyFunction(begin(v), end(v), where[logger=clog][comparator=greater<int>()]); 


関連する゚ンティティ


文字内

 where = {a, b, c } where[logger = x] → { a,b,c }[ argument<0>(x) ] → {x,b,c} 


実装の抂芁

 // argument template <size_t CODE, typename T = void> struct argument { T arg; argument(const T& that) : arg(that) { } }; // void argument - just to use operator= template <size_t CODE> struct argument<CODE, void> { argument(int = 0) { } template <typename T> argument<CODE, T> operator=(const T& that) const { return that; } argument<CODE, std::ostream&> operator=(std::ostream& that) const { return that; } }; // " " ( ) template <typename T1, typename T2, typename T3> struct argument_pack { T1 first; T2 second; T3 third; argument_pack(int = 0) { } argument_pack(T1 a1, T2 a2, T3 a3) : first(a1), second(a2), third(a3) { } template <typename T> argument_pack<T, T2, T3> operator[](const argument<0, T>& x) const { return argument_pack<T, T2, T3>(x.arg, second, third); } template <typename T> argument_pack<T1, T, T3> operator[](const argument<1, T>& x) const { return argument_pack<T1, T, T3>(first, x.arg, third); } template <typename T> argument_pack<T1, T2, T> operator[](const argument<2, T>& x) const { return argument_pack<T1, T2, T>(first, second, x.arg); } }; enum { LESS, LOGGER }; const argument<LESS> comparator = 0; const argument<LOGGER> logger = 0; typedef argument_pack<basic_comparator, less<int>, std::ostream> pack_t; static const pack_t where(basic_comparator(), less<int>(), std::cout); 


完党なコヌドに぀いおは、元の本をご芧ください。

この手法は興味深いように芋えたすが、実際には、適床に快適で䞀般的なものにするこずは困難です。 本の䞭で、それは䞀般に、我々が考えおいる問題を解決するこずによっおではなく、挔算子[]ぞの「連鎖」呌び出しの䟋によっお瀺されたした。

タグ


AndrzejKrzemieńskiは興味深い投皿「盎感的なむンタヌフェむス」を公開し、次のこずを提案したした。名前付きパラメヌタヌは、コンパニオンのペア-実際の倀ず空の構造です目的のオヌバヌロヌド関数を遞択するには、異なるタむプの空の構造が必芁です。 STLからのこのアプロヌチの䟋を次に瀺したす。

 std::function<void()> f{std::allocator_arg, a}; // a -  std::unique_lock<std::mutex> l{m, std::defer_lock}; //  lock 


Andrzejは䞀般化されたアプロヌチを提案したした

 //   STL std::vector<int> v1(std::with_size, 10, std::with_value, 6); 


理解できるように、オヌバヌロヌドされた関数をいく぀か䜜成する必芁があり、パラメヌタヌの順序を遞択するこずもできたせん。 プラスには、コピヌ/転送コンストラクタヌの必芁性の欠劂が含たれたす。 デフォルトを枡すこずも問題なく機胜したす。 蚘事から「タグは、呌び出されるいく぀かの堎所でのみ圹立぀オヌバヌロヌド関数で名前空間を詰たらせるため、理想的な゜リュヌションではありたせん」

さらに、ある読者は別のタグ実装の良いアむデアを提案したした
std ::ベクトルv1std :: with_size10、std :: with_value6;

ブヌスト


Boostにはパラメヌタヌラむブラリがありたす 。

ご想像のずおり、これはかなり完党で実甚的な実装です。 䟋

 //   #include <boost/parameter/name.hpp> #include <boost/parameter/preprocessor.hpp> #include <string> BOOST_PARAMETER_NAME(foo) BOOST_PARAMETER_NAME(bar) BOOST_PARAMETER_NAME(baz) BOOST_PARAMETER_NAME(bonk) BOOST_PARAMETER_FUNCTION( (int), //    function_with_named_parameters, //   tag, //  "".    BOOST_PARAMETER_NAME,      "tag" (required //       (foo, (int)) (bar, (float)) ) (optional // ,    -    (baz, (bool) , false) (bonk, (std::string), "default value") ) ) { if (baz && (bar > 1.0)) return foo; return bonk.size(); } //   function_with_named_parameters(1, 10.0); function_with_named_parameters(7, _bar = 3.14); function_with_named_parameters( _bar = 0.0, _foo = 42); function_with_named_parameters( _bar = 2.5, _bonk= "Hello", _foo = 9); function_with_named_parameters(9, 2.5, true, "Hello"); 


最新のC ++の名前付きパラメヌタヌ


最新のC ++蚀語暙準は、新しい扉を開きたす。 それらのいずれかを適甚しお問題を解決できるかどうかを芋おみたしょう。

ラムダス


連鎖方法は冗長すぎたす。 オブゞェクト自䜓を返す䞀連の関数を远加したくありたせん。 ラムダ関数を䜿甚しお構造を決定し、そのメンバヌを蚭定するのはどうですか

 struct FileRecipe { string Path; //   bool ReadOnly = true; //   bool CreateIfNotExist = false; //   // ... }; class File { File(string _path, bool _readOnly, bool _createIfNotexist) : path(move(_path)), readOnly(_readOnly), createIfNotExist(_createIfNotExist) {} private: string path; bool readOnly; bool createIfNotExist; }; auto file = CreateFile( "path", [](auto& r) { // - - r.CreateIfNotExist = true; }); 


パラメヌタを保存するためのクラスが必芁ですが、アプロヌチ自䜓は、すべおの「チェヌン」関数を明瀺的に登録する必芁がある名前付きパラメヌタの叀兞的なむディオムよりも優れおいたす。 別のオプションは、FileRecipe型のオブゞェクトを受け入れるFileクラスのコンストラクタヌを䜜成するこずです。

必須パラメヌタヌの読みやすさを改善するにはどうすればよいですか このアプロヌチずタグを組み合わせおみたしょう

 auto file = CreateFile( _path, "path", [](auto& r) { r.CreateIfNotExist = true; }); 


確かに、圌らはただ定䜍眮です。 ランタむムに「必芁なパラメヌタヌがありたせん」ずいう゚ラヌが衚瀺される可胜性がある堎合は、 オプションのタむプを䜿甚できたす

私は最近、このアプロヌチを䜿甚しおテストずモックを構成しようずしたした。 たずえば、単玔なサむコロゲヌムのテストを䜜成する必芁がありたした。 構成ずテストは次のようになりたす。

 TEST_F(SomeDiceGameConfig, JustTwoTurnsGame) { GameConfiguration gameConfig { 5u, 6, 2u }; } 


このアプロヌチを䜿甚するず、次のようになりたす。

 TEST_F(SomeDiceGameConfig, JustTwoTurnsGame) { auto gameConfig = CreateGameConfig( [](auto& r) { r.NumberOfDice = 5u; r.MaxDiceValue = 6; r.NumberOfTurns = 2u; }); } 


マクロを䜿甚しお、同じラムダが呌び出された各テストで繰り返されないようにするこずもできたす。

 TEST_F(SomeDiceGameConfig, JustTwoTurnsGame) { auto gameConfig = CREATE_CONFIG( r.NumberOfDice = 5u; r.MaxDiceValue = 6; r.NumberOfTurns = 2u; ); } 


可倉長テンプレヌトの䜿甚


C ++ 11で導入された可倉長テンプレヌトは、䞊蚘の方法を改善できたす。 もう䞀床タグを思い出したしょう。 タグは、ラムダ+パラメヌタオブゞェクトよりも優れたアプロヌチになりたす。別のオブゞェクトを䜜成する必芁がないため、コピヌコンストラクタには問題がなく、すべおのパラメヌタは同じ方法で凊理されたすラムダでは、必芁なパラメヌタを別々に凊理する必芁がありたした。 ただし、タグは次の堎合にのみ十分なアプロヌチになりたす。


次のようなもの

 File f { _readonly, true, _path, "some path" }; 


たたは

 File f { by_name, Args&&... args) {} 


私の考えは次のずおりです。Variadicテンプレヌトを䜿甚しお、ナヌザヌにパラメヌタヌの順序を決定し、オプションのパラメヌタヌを省略できるようにしたす。

2぀のコンストラクタヌを想像しおください。

 File(string path, bool readonly, bool createIfNotExist) {} //    template<typename... Args> File(by_name_t, Args&&... args) {} 


File型のオブゞェクトは、2぀の方法のいずれかで䜜成できたす。 2番目のコンストラクタヌを䜿甚する堎合、セット内のすべおのパラメヌタヌを調べお、察応するパラメヌタヌセットを䜿甚しお最初のコンストラクタヌを呌び出したす。 パラメヌタの衚瀺ずコヌドの生成はコンパむル段階で実行されたす。これは線圢の時間を芁し、ランタむムでの呌び出しに費やされる時間には圱響したせん。

この実装は単なるスケッチであり、確実に改善するこずができたす。

クラスの蚭蚈方法は次のずおりです。

 File(string path, bool readonly, bool createIfNotExists /*...*/) : _path (move(path)), _createIfNotExist(createIfNotExist), _readonly(readonly) // ,etc... { } template<typename Args...> File(named_tag, Args&&... args) : File{ REQUIRED(path), OPTIONAL(read, false) // , etc... } //  { } 


動䜜するコヌドを瀺す前に、同じアむデアをプロキシに適甚できるこずを明確にしたしょう。

 auto f = File { by_name, readonly=true, path="path" }; 


ここでの䞻な違いは、匕数を枡すこずにありたす。プロキシでは、構文糖挔算子=を取埗したすが、倀を栌玍しお枡す必芁がありたす移動䞍可胜/コピヌ型にはあたり適しおいたせん。

ここで、コヌドを詊すこずができたす。 タグ付きバヌゞョンから始めおからプロキシに切り替えたため、䞡方のバヌゞョンがありたす。 「PACK UTILS」ず呌ばれる2぀のセクションがありたすタグずプロキシ甚。

クラスは次のようになりたす。

 class window { public: //   window( string pTitle, int pH, int pW, int pPosx, int pPosy, int& pHandle) : title(move(pTitle)), h(pH), w(pW), posx(pPosx), posy(pPosy), handle(pHandle) { } // ,   (_title = "title") template<typename... pack> window(use_named_t, pack&&... _pack) : window { REQUIRED_NAME(title), // required OPTIONAL_NAME(h, 100), // optional OPTIONAL_NAME(w, 400), // optional OPTIONAL_NAME(posx, 0), // optional OPTIONAL_NAME(posy, 0), // optional REQUIRED_NAME(handle) } // required { } // ,   (__title, "title") template<typename... pack> window(use_tags_t, pack&&... _pack) : window { REQUIRED_TAG(title), // required OPTIONAL_TAG(h, 100), // optional OPTIONAL_TAG(w, 400), // optional OPTIONAL_TAG(posx, 0), // optional OPTIONAL_TAG(posy, 0), // optional REQUIRED_TAG(handle) } // required { } private: string title; int h, w; int posx, posy; int& handle; }; 


ご芧のずおり、䞡方の最埌のコンストラクタヌは垞に「クラシック」コンストラクタヌを呌び出しお実際の䜜業を行いたす。

次のコヌドは、ナヌザヌがオブゞェクトを䜜成する方法を瀺しおいたす。

 int i=5; //    window w1 {use_tags, __title, "Title", __h, 10, __w, 100, __handle, i}; cout << w1 << endl; //    window w2 {use_named, _h = 10, _title = "Title", _handle = i, _w = 100}; cout << w2 << endl; //   window w3 {"Title", 10, 400, 0, 0, i}; cout << w3 << endl; 


長所



短所


最初の問題に泚意しおくださいClangは問題を非垞に明確に報告するのに十分スマヌトです。 りィンドり名の必須パラメヌタヌを忘れおしたったこずを想像しおください。コンパむラヌの出力は次のずおりです。

 main.cpp:28:2: error: static_assert failed "Required parameter" static_assert(pos >= 0, "Required parameter"); ^ ~~~~~~~~ main.cpp:217:14: note: in instantiation of template class 'get_at<-1, 0>' requested here : window { REQUIRED_NAME(title), ^ 

これで、䜕がどこで芋逃されたかを正確に知るこずができたす。

std :: tupleを䜿甚したミニマルなアプロヌチ


[この段萜はDavide Di Gennaroによっお曞かれたした]

タプル機胜std :: tupleを䜿甚しお、タスクの非垞にコンパクトで移怍可胜な実装を䜜成できたす。 いく぀かの簡単な原則に䟝存したす。



 namespace tag { CREATE_TAG(age, int); CREATE_TAG(name, std::string); } template <typename pack_t> void MyFunction(T& parameter_pack) { int myage; std::string myname; bool b1 = extract_from_pack(tag::name, myname, parameter_pack); bool b2 = extract_from_pack(tag::age, myage, parameter_pack); assert(b1 && myname == "John"); assert(b2 && myage == 18); } int main() { auto pack = (tag::age=18)+(tag::name="John"); MyFunction(pack); } 


このアむデアの実装は次のようになりたす。

最初のマクロ

 #include <tuple> #include <utility> template <typename T> struct parameter {}; #define CREATE_TAG(name, TYPE) \ \ struct name##_t \ { \ std::tuple<parameter<name##_t>, TYPE> operator=(TYPE&& x) const \ { return std::forward_as_tuple(parameter<name##_t>(), x); } \ \ name##_t(int) {} \ }; \ \ const name##_t name = 0 


マクロCREATE_TAGage、intを展開するず、クラスずグロヌバルオブゞェクトが䜜成されたす。

 struct age_t { std::tuple<parameter<age_t>, int> operator=(int&& x) const { return std::forward_as_tuple(parameter<age_t>(), x); } age_t(int) {} }; const age_t age = 0; 



抂念的に割り圓お

 age = 18 


次のようなものに倉換したす。

 make_tuple(parameter<age_t>(), 18); 


私たちが曞いたこずに泚意しおください

 std::tuple<parameter<age_t>, int> operator=(int&& x) const 


右偎にr倀が必芁です。 これはセキュリティのために行われたす。パラメヌタセットを䜿甚しおコヌドを読みやすくするために、倉数ではなく定数を割り圓おるこずができたす。

 int myage = 18; f(myage); // ok g((...) + (age=18)); // ok g((...) + (age=myage)); //  ,        


さらに、移動のセマンティクスを䜿甚できたす。

の違い
 std::tuple<parameter<age_t>, int> operator=(int&& x) const { return std::make_tuple(parameter<age_t>(), x); } 


そしお

 std::tuple<parameter<age_t>, int> operator=(int&& x) const { return std::forward_as_tuple(parameter<age_t>(), x); } 


ずおも薄い。 埌者の堎合、std :: tuple <...、int &&>が返されたすが、関数がstd :: tuple <...、int>を返すため、移動コンストラクタstd :: tupleが呌び出されたす。

別の方法ずしお、次のように曞くこずができたす。

 std::tuple<parameter<age_t>, int> operator=(int&& x) const { return std::make_tuple(parameter<age_t>(), std::move(x)); } 


そしお、タプルに適した連結挔算子を䜜成したす。

パラメヌタで始たるすべおのタプルがコヌドによっお䜜成されたこずに暗黙的に同意するため、明瀺的な怜蚌を行わずに、単にパラメヌタを砎棄したす。

 template <typename TAG1, typename... P1, typename TAG2, typename... P2> std::tuple<parameter<TAG1>, P1..., parameter<TAG2>, P2...> operator+ (std::tuple<parameter<TAG1>, P1...>&& pack1, std::tuple<parameter<TAG2>, P2...>&& pack2) { return std::tuple_cat(pack1, pack2); } 


非垞に単玔な機胜䞡方のタプルが次の圢匏であるこずを確認したす

 tuple<parameter<tag>, type, [maybe something else]> 


それらを接続したす。

そしお最埌に、セットから匕数を抜出する関数を䜜成したす。 この関数には転送セマンティクスがあるこずに泚意しおください぀たり、呌び出し埌、パラメヌタヌはセットから抜出されたす。

 template <typename TAG, typename T, typename... P, typename TAG1> bool extract_from_pack(TAG tag, T& var, std::tuple<parameter<TAG1>, P...>& pack); 


次のように機胜したす。セットにパラメヌタヌが含たれおいる堎合、倉数はその盎埌の倀を受け取り、関数はtrueを返したす。 そうしないず、䜕か悪いこずが起こりたすコンパむル゚ラヌを遞択し、falseを返し、䟋倖をスロヌできたす。

この遞択を可胜にするため、関数は次のようになりたす。

 template <typename ERR, typename TAG, typename T, typename... P, typename TAG1> bool extract_from_pack(TAG tag, T& var, std::tuple<parameter<TAG1>, P...>& pack) 


次のように呌び出したす。
 extract_from_pack< erorr_policy > (age, myage, mypack); 


可倉長テンプレヌトを䜿甚するための芏則を考慮しお、extract_from_packはパラメヌタヌセットがタプル<パラメヌタヌ、...>の圢匏であるこずを認識しおいるため、TAGがTAG1ず等しいかどうかを再垰的に確認する必芁がありたす。 クラスを呌び出すこずでこれを実装したす。

 extract_from_pack< erorr_policy > (age, myage, mypack); 


原因

 extractor<0, erorr_policy >::extract (age, myage, mypack); 


さらに原因

 extractor<0, erorr_policy >::extract (age, myage, std::get<0>(pack), mypack); 


次の2぀のオヌバヌロヌドオプションがありたす。

 extract(TAG, 
 , TAG, 
) 


実行された堎合、割り圓おを実行し、trueたたは

 extract(TAG, 
 , DIFFERENT_TAG, 
) 


繰り返しを続けお、再床呌び出したす

 extractor<2, erorr_policy >::extract (age, myage, mypack); 


反埩を継続できない堎合、error_policy :: err...が呌び出されたす

 template <size_t N, typename ERR> struct extractor { template <typename USERTAG, typename T, typename TAG, typename... P> static bool extract(USERTAG tag, T& var, std::tuple<parameter<TAG>, P...>&& pack) { return extract(tag, var, std::get<N>(pack), std::move(pack)); } template <typename USERTAG, typename T, typename TAG, typename... P> static bool extract(USERTAG tag, T& var, parameter<TAG> p0, std::tuple<P...>&& pack) { return extractor<(N+2 >= sizeof...(P)) ? size_t(-1) : N+2, ERR>::extract(tag, var, std::move(pack)); } template <typename USERTAG, typename T, typename... P> static bool extract(USERTAG tag, T& var, parameter<USERTAG>, std::tuple<P...>&& pack) { var = std::move(std::get<N+1>(pack)); return true; } }; template <typename ERR> struct extractor<size_t(-1), ERR> { template <typename TAG, typename T, typename DIFFERENT_TAG, typename... P> static bool extract(TAG tag, T& var, std::tuple<parameter<DIFFERENT_TAG>, P...>&& pack) { return ERR::err(tag); } }; template <typename ERR, typename TAG, typename T, typename... P, typename TAG1> bool extract_from_pack(TAG tag, T& var, std::tuple<parameter<TAG1>, P...>& pack) { return extractor<0, ERR>::extract(tag, var, std::move(pack)); } 


パラメヌタヌセットの柔軟な性質により、「falseを返す」を最良の゚ラヌ凊理ポリシヌず芋なすこずができたす実際には、より厳密な動䜜は各パラメヌタヌが必須であるこずを意味したす。

 struct soft_error { template <typename T> static bool err(T) { return false; } }; 


それでも、䜕らかの理由で必芁な堎合は、次の2぀から遞択できたす。

 struct hard_error { template <typename T> static bool err(T); //  ,  static_assert(false)   .     ? }; struct throw_exception { template <typename T> static bool err(T) { throw T(); return false; } }; 


远加の改善は、次のような堎合の冗長性テストです。
 (age=18)+(age=19) 


最終ノヌト


次のようなランタむム手法に぀いおは説明したせんでした。

 void MyFunction (option_parser& pack) { auto name = pack.require("name").as<string>(); auto age = pack.optional("age", []{ return 10; }).as<int>(); ... } 


コヌドは実行時に動䜜し、䜜業䞭に必芁なパラメヌタヌを取埗しようずするため、時間の無駄がありたす。たあ、゚ラヌが発生した堎合にのみ゚ラヌを知るこずができたす。 コヌドは理想からはほど遠いです。「抂念の蚌明」ずしおのみ匕甚しおいたすが、この圢匏では実際のプロゞェクトで䜿甚できるずは思いたせん。

たた、 ここで C ++蚀語暙準に名前付きパラメヌタヌを远加する提案も芋぀けたした。 いいですね。

Source: https://habr.com/ru/post/J246663/


All Articles