📜 ⬆️ ⬇️

Working with C ++ tuples (std :: tuple). Functions foreach, map and call

Here I will talk about working with C ++ tuples ( tuple ), give some useful functions that can make life much easier when using tuples, and also give examples of using these functions. All from personal experience.

Foreach


To iterate over all the elements of a tuple, calling for each one the same function - probably the first task faced by the developer when using tuples. The implementation is very straightforward:

namespace tuple_utils { //  'callback'     /* struct callback { template<std::size_t, class T> void operator()( T&& element ) { // do something } }; tupleForeach( callback(), myTuple ); */ template<class TCallback, class ...TParams> void tupleForeach( TCallback& callback, const std::tuple<TParams...>& tuple ); namespace { template<std::size_t Index, class TCallback, class ...TParams> struct _foreach_ { static void tupleForeach_( TCallback& callback, const std::tuple<TParams...>& tuple ) { //      callback'a       const std::size_t idx = sizeof...( TParams ) - Index; callback.operator()<idx>( std::get<idx>( tuple ) ); _foreach_<Index - 1, TCallback, TParams...>::tupleForeach_( callback, tuple ); } }; template<class TCallback, class ...TParams> struct _foreach_<0, TCallback, TParams...> { static void tupleForeach_( TCallback& /*callback*/, const std::tuple<TParams...>& /*tuple*/ ) {} }; } // template<class TCallback, class ...TParams> void tupleForeach( TCallback& callback, const std::tuple<TParams...>& tuple ) { _foreach_<sizeof...( TParams ), TCallback, TParams...>::tupleForeach_( callback, tuple ); } } // tuple_utils 

Here, the auxiliary structure of _foreach_ is used , having the next tuple index as an additional template parameter. Its only static method tupleForeach_ calls the function specified by the callback for the element with this index, after which it is called recursively. Partial specialization of this structure for an index equal to zero is degenerate and is the completion of recursion.

Example 1. Banal
 struct ForeachCallback { template<std::size_t Index, class T> void operator()( T&& element ) { std::cout << "( " << Index << " : " << element << " ) "; } }; void foo() { auto myTyple = std::make_tuple( 42, 3.14, "boo" ); tuple_utils::tupleForeach( ForeachCallback(), myTyple ); } 

Example 2. Checking getters
 //   getter'      template<class TResult, class TOwner> using TGetter = TResult( TOwner::* )() const; // ,  getter'   template<class TGetterOwner, class ...TParams> class MyGetterContainer { //   getter'     template<class TResult> using TMyGetter = TGetter<TResult, TGetterOwner>; ..... private: ..... // ,    getter'  ( nullptr) void checkGetters(); //  getter'   (..    ) std::tuple<TMyGetter<TParams>...> m_getters; }; namespace { // callback,    getter' template<class TGetterOwner> struct GetterCheckCallback { //   ,      getter' //     'Index'       template<std::size_t Index, class T> void operator()( const TGetter<T, TGetterOwner>& element ) { assert( element != nullptr ); } }; } // template<class TGetterOwner, class ...TParams> void MyGetterContainer<TGetterOwner, TParams...>::checkGetters() { //  callback    getter' tuple_utils::tupleForeach( GetterCheckCallback<TGetterOwner>(), m_getters ); } 

Map


Another task that comes to mind for the convenience of working with tuples is a function that builds a new tuple sequentially from the results of performing a given function on the elements of a given tuple. A very common task in functional languages. Its implementation is perhaps even simpler:
')
 namespace tuple_utils { //    (  )    'callback'      'sourceTuple' /* struct callback { template<std::size_t, class R, class T> R operator()( T&& element ) { // do something } }; mapTuple( callback(), myTuple ); */ template<class TCallback, class TSourceTuple> auto mapTuple( TCallback& callback, const TSourceTuple& sourceTuple ); namespace { template<class TCallback, class TSourceTuple, std::size_t... Indices> auto mapTuple_( TCallback& callback, const TSourceTuple& sourceTuple, std::index_sequence<Indices...> ) { return std::make_tuple( callback.operator()<Indices>( std::get<Indices>( sourceTuple ) )... ); } } // template<class TCallback, class TSourceTuple> auto mapTuple( TCallback& callback, const TSourceTuple& sourceTuple ) { return mapTuple_( callback, sourceTuple, std::make_index_sequence<std::tuple_size<TSourceTuple>::value>() ); } } // tuple_utils 

Here we use the auxiliary function mapTuple_ , which takes an additional parameter, a set of all indexes of the tuple, through index_sequence . The resulting tuple is formed from the results of the execution of the function specified through callback over the elements for each of the indices.

Example 1. Banal
 struct MapCallback { template<std::size_t Index, class T> std::string operator()( T&& element ) { std::stringstream ss; ss << "( " << Index << " : " << element << " )"; std::string result; result << ss; return result; } }; void foo() { auto sourceTyple = std::make_tuple( 42, 3.14, "boo" ); auto strTuple = tuple_utils::mapTuple( MapCallback(), sourceTyple ); } 

Example 2. Forming a tuple of getter values
 //   getter'      template<class TResult, class TOwner> using TGetter = TResult( TOwner::* )() const; // ,  getter'   template<class TGetterOwner, class ...TParams> class MyGetterContainer { //   getter'     template<class TResult> using TMyGetter = TGetter<TResult, TGetterOwner>; ..... protected: ..... //    getter' std::tuple<TParams...> getterValues() const; private: ..... //  ,     getter' TGetterOwner& m_getterOwner; //  getter'   (..    ) std::tuple<TMyGetter<TParams>...> m_getters; }; namespace { // callback,   getter' template<class TGetterOwner> struct GetterValuesCallback { public: //  GetterValuer( const TGetterOwner& getterOwner ) : m_getterOwner( getterOwner ) { } //  ,   getter';      getter' //     'Index'       template<std::size_t Index, class T> T operator()( const TGetter<T, TGetterOwner>& oneGetter ) { return ( m_getterOwner.*oneGetter )(); } private: const TGetterOwner& m_getterOwner; }; } // template<class TGetterOwner, class ...TParams> std::tuple<TParams...> MyGetterContainer<TGetterOwner, TParams...>::getterValues() const { //  callback         getter' return tuple_utils::mapTuple( GetterValuesCallback<TGetterOwner>( m_getterOwner ), m_getters ); } 

Call


What else would like to “be able to do” with tuples is to use their contents as parameters for calling a function (of course, the order and type of arguments corresponds to the order and type of elements of the tuple). The implementation of this function is very similar to the implementation of the map function:

 namespace tuple_utils { //  'callback',       'tuple' /* struct callback { template<class TResult, class ...TParams> TResult operator()( TParams... ) { // do something } }; callTuple( callback(), myTuple ); */ template<class TCallback, class ...TParams> auto callTuple( TCallback& callback, const std::tuple<TParams...>& tuple ); namespace { template<class TCallback, class TTuple, std::size_t... Indices> auto callTuple_( TCallback& callback, const TTuple& tuple, std::index_sequence<Indices...> ) { return callback( std::get<Indices>( tuple )... ); } } // template<class TCallback, class ...TParams> auto callTuple( TCallback& callback, const std::tuple<TParams...>& tuple ) { return callTuple_( callback, tuple, std::index_sequence_for<TParams...>() ); } } // tuple_utils 

Here, as in the case of map , the auxiliary function callTuple_ is used , which takes an additional parameter, a set of all indexes of the tuple, via the index_sequence . It calls the function specified via callback , passing to it all elements of the tuple corresponding to the indices. The result of its execution is the result of the execution of the transferred function.

Example 1. Banal
 bool checkSomething( int a, float b, const std::string& txt ); struct CallCallback { template<class TResult, class ...TParams> TResult operator()( TParams... params ) { return checkSomething( params... ); } }; void foo() { std::tuple<int, float, std::string> paramsTyple = std::make_tuple( 42, 3.14, "boo" ); bool isParamsValid = tuple_utils::callTuple( CallCallback(), paramsTyple ); } 

Example 2. Calling setter with getter value parameters
 // ,  getter'   template<class TGetterOwner, class ...TParams> class MyGetterContainer { ..... protected: ..... //    getter' std::tuple<TParams...> getterValues() const; ..... }; //   setter'   void- c  template<class TOwner, class ...TParams> using TSetter = void( TOwner::* )( TParams... ); // ,  setter     getter'  template<class TSetterOwner, class TGetterOwner, class ...TParams> class MySetterCaller : public MyGetterContainer<TGetterOwner, TParams...> { //   getter'        using TMySetter = TSetter<TSetterOwner, TParams...>; ..... public: ..... //  setter   getter' void callSetter(); private: ..... //  ,     setter TSetterOwner& m_setterOwner; //  setter TMySetter m_setter; }; namespace { // callback,   setter' template<class TSetterOwner, class ...TParams> struct CallSetterCallback { public: //  GetterPasser( TSetterOwner& setterOwner, TSetter<TSetterOwner, TParams...> setter ) : m_setterOwner( setterOwner ), m_setter( setter ) { } //  ,   setter' void operator()( TParams... params ) { return ( m_setterOwner.*m_setter )( params... ); } private: TSetterOwner& m_setterOwner; TSetter<TSetterOwner, TParams...> m_setter; }; } // template<class TSetterOwner, class TGetterOwner, class ...TParams> void MySetterCaller<TSetterOwner, TGetterOwner, TParams...>::callSetter() { //     getter' std::tuple<TParams...> _getterValues = getterValues(); //  callback   setter'    tuple_utils::callTuple( CallSetterCallback( m_setterOwner, m_setter ), _getterValues ); } 

PS In C ++ 17, std :: apply will be available, which performs the same functionality.

General remarks




One more example. Converting Method Handlers to Functor Handlers
 //   -  void-    template<class TObject, class TValue> using TMethodHandler = void( TObject::* )( const TValue& ); // ,  - template<class ...TValues> class MyHandlerContainer { public: // ;     - MyHandlerContainer( const std::function<void( const TValues& )>... handlers ); ..... //       - template<class TMethodOwner> static MyHandlerContainer<TValues...>* createFrom( TMethodOwner& methodOwner, TMethodHandler<TMethodOwner, TValues>... handlers ); ..... }; namespace { // callback    - template<class TMethodOwner> struct CheckCallback { //  CheckCallback() : IsValid( true ) { } //     - template<std::size_t Index, class TValue> void operator()( const TMethodHandler<TMethodOwner, TValue>& oneMethodHandler ) { if( oneMethodHandler == nullptr ) IsValid = false; } bool IsValid; } // callback    -   - template<class TMethodOwner> struct FunctorHandlerCallback { public: //  FunctorHandlerCallback( TMethodOwner& methodOwner ) : m_methodOwner( methodOwner ) { } //   -  - template<std::size_t Index, class TValue> std::function<void( const TValue& )> operator()( const TMethodHandler<TMethodOwner, TValue>& oneHandlers ) { return [ this, oneHandlers ]( const TValue& tValue ) { ( m_methodOwner.*oneHandlers )( tValue ); }; } private: TMethodOwner& m_methodOwner; }; // callback     'MyHandlerContainer'   - template<class ...TValues> struct CreateCallback { //     'MyHandlerContainer'   - auto operator()( std::function<void( const TValues& )>... handlers ) { return new MyHandlerContainer<TValues...>( handlers... ); } }; } // template<class ...TValues> template<class TMethodOwner> MyHandlerContainer<TValues...>* MyHandlerContainer<TValues...>::createFrom( TMethodOwner& methodOwner, TMethodHandler<TMethodOwner, TValues>... handlers ) { //  - auto methodsTuple = std::make_tuple( handlers... ); // ,     CheckCallback checkCallback; tuple_utils::tupleForeach( checkCallback, methodsTuple ); //     if( checkCallback.IsValid ) { // (,        ) FunctorHandlerCallback<TMethodOwner>* functorHandlerCallback = new FunctorHandlerCallback<TMethodOwner>( methodHolder ); //  - auto handlersTuple = tuple_utils::mapTuple( *functorHandlerCallback, methodsTuple ); //    -   'MyHandlerContainer' MyHandlerContainer<TValues...>* result = tuple_utils::callTuple( CreateCallback<TValues...>( multiProperty ), handlersTuple ); return result; } //      assert( false ); return nullptr; } 


Implementation without index_sequence


index_sequence appears only in C ++ 14. If you want to use these functions in C ++ 11 (in which the tuple appeared), either for some other reason you do not want to use index_sequence , or it’s just interesting to see the implementation of the map and call functions without them, here’s the implementation:

Map
 namespace tuple_utils { //   tuple (tuple  )    'callback'    tuple' /* struct callback { template<std::size_t, class R, class T> R operator()( T&& element ) { // do something } }; mapTuple( callback(), myTuple ); */ template<class TCallback, class TSourceTuple> auto mapTuple( TCallback& callback, const TSourceTuple& sourceTuple ); namespace { template<std::size_t Index, class TCallback, class TSourceTuple, std::size_t... Indices> struct _map_ { auto static mapTuple_( TCallback& callback, const TSourceTuple& sourceTuple ) { const std::size_t idx = std::tuple_size<TSourceTuple>::value - Index; return _map_<Index - 1, TCallback, TSourceTuple, Indices..., idx>::mapTuple_( callback, sourceTuple ); } }; template<class TCallback, class TSourceTuple, std::size_t... Indices> struct _map_<0, TCallback, TSourceTuple, Indices...> { auto static mapTuple_( TCallback& callback, const TSourceTuple& sourceTuple ) { return std::make_tuple( callback.operator()<Indices>( std::get<Indices>( sourceTuple ) )... ); } }; } // template<class TCallback, class TSourceTuple> auto mapTuple( TCallback& callback, const TSourceTuple& sourceTuple ) { return _map_<std::tuple_size<TSourceTuple>::value, TCallback, TSourceTuple>::mapTuple_( callback, sourceTuple ); } } // tuple_utils 

Call
 namespace tuple_utils { //  'callback',      tuple /* struct callback { template<class TResult, class ...TParams> TResult operator()( TParams... params ) { // do something } }; callTuple( callback(), myTuple ); */ template<class TCallback, class TResult, class ...TParams> TResult callTuple( TCallback& callback, const std::tuple<TParams...>& tuple ); namespace { template<std::size_t Index, class TCallback, class TResult, class TTuple, class ...TParams> struct _call_ { static TResult callTuple_( TCallback& callback, const TTuple& tuple, TParams... params ) { const std::size_t idx = std::tuple_size<TTuple>::value - Index; return _call_<Index - 1, TCallback, TResult, TTuple, TParams..., typename std::tuple_element<idx, TTuple>::type>::callTuple_( callback, tuple, params..., std::get<idx>( tuple ) ); } }; template<class TCallback, class TResult, class TTuple, class ...TParams> struct _call_<0, TCallback, TResult, TTuple, TParams...> { static TResult callTuple_( TCallback& callback, const TTuple& tuple, TParams... params ) { return callback( params... ); } }; } // template<class TCallback, class TResult, class ...TParams> TResult callTuple( TCallback& callback, const std::tuple<TParams...>& tuple ) { return _call_<sizeof...( TParams ), TCallback, TResult, std::tuple<TParams...>>::callTuple_( callback, tuple ); } } // tuple_utils 

The approach to implementing these functions is the same: we manually “accumulate” indices (instead of index_sequence ) or parameters, and then, at the end of the recursion, perform the necessary actions with the already obtained set of indices / parameters. Although I personally approach with the index seems more universal.

Thank you for your time!

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


All Articles