// -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 4 -*- // (c) 2014 Vladimír Štill /* This header provides functionality of unpacking tuples into function * arguments. * * It define 3 main functions: * pass( fn, tuple ) takes callable object and tuple and unpacks tuple * into arguments of fn * * pass( x, method, tuple ) which acts like previous version, except it * calls method pointer method on x (which can be either pointer or object * directly) * * passIx< ixs... >( ... ) which acts exactly like (one of) pass functions * above but forwards only values on specified indices (in order) * * * In all cases exact prototype of function which can be called depends on * type of tuple: * - if tuple is value or rvalue reference, none of the arguments in function * shall be non-const lvalue references (that is, tuple constents are * forwarded as rvalues) * - if tuple is lvalue reference, none of the arguments in function shall * be rvalue references (that is, tuple contends are forwarded as lvalue) * - const qualification of tuple is preserved * * Furthermore, following functions and types are provided and not considered * implementation details: * template< int... > struct Indices; for holding indices of tuple * * template< typename Tuple > GetIndices; for obtaining indices of tuple * - defines single nested name Ix of form Indices< ix... > for some ix * which represents indices of tuple (if Tuple is in form std::tuple< ... >) * - otherwise is empty type with no nested names * * bindThis( x, method ) produces callable object which calls x.*m( ... ) * */ #include #include #include #ifndef BRICK_TUPLE_H #define BRICK_TUPLE_H namespace brick { namespace tuple { template< int... Ixs > struct Indices { static constexpr int size = sizeof...( Ixs ); }; template< int... > struct _Expand { }; template< int... is > struct _Expand< 0, is... > { using Ix = Indices< 0, is... >; }; template< int i, int... is > struct _Expand< i, is... > { static_assert( i > 0, "invalid param in _Expand" ); using Ix = typename _Expand< i - 1, i, is... >::Ix; }; template< typename... > struct _GetIndices { }; template< typename T, typename... Ts > struct _GetIndices< T, Ts... > { using Ix = typename _Expand< sizeof...( Ts ) >::Ix; }; template<> struct _GetIndices<> { using Ix = Indices<>; }; template< typename > struct GetIndices; template< typename... Args > struct GetIndices< std::tuple< Args... > > : _GetIndices< Args... > { }; template< typename > struct _Pass { }; template< int... ix > struct _Pass< Indices< ix... > > { template< typename Fn, typename Tuple > static auto call( Fn &&fn, typename std::remove_reference< Tuple >::type &tuple ) -> decltype( fn( std::get< ix >( tuple )... ) ) { return fn( std::get< ix >( tuple )... ); } template< size_t i, typename Tuple > static auto getrvalue( Tuple &tuple ) -> typename std::tuple_element< i, Tuple >::type && { return static_cast< typename std::tuple_element< i, Tuple >::type && >( std::get< i >( tuple ) ); } template< typename Fn, typename Tuple > static auto call( Fn &&fn, typename std::remove_reference< Tuple >::type &&tuple ) -> decltype( fn( getrvalue< ix >( tuple )... ) ) { return fn( getrvalue< ix >( tuple )... ); } }; template< typename T, typename Method > class BindThis { T _self; Method _m; public: BindThis( T &&self, Method &&m ) : _self( std::forward< T >( self ) ), _m( std::forward< Method >( m ) ) { } template< typename... Args > auto operator()( Args &&... args ) -> decltype( (std::declval< T >().*std::declval< Method >())( std::forward< Args >( args )... ) ) { return (_self.*_m)( std::forward< Args >( args )... ); } }; template< typename T, typename Method > auto bindThis( T &&self, Method &&method ) -> typename std::enable_if< std::is_member_function_pointer< Method >::value, BindThis< T, Method > >::type { return BindThis< T, Method >( std::forward< T >( self ), std::forward< Method >( method ) ); } template< typename T, typename Method > auto bindThis( T *self, Method &&method ) -> typename std::enable_if< std::is_member_function_pointer< Method >::value, BindThis< T &, Method > >::type { return BindThis< T &, Method >( *self, std::forward< Method >( method ) ); } template< typename Fn, typename Tuple, typename Ix = typename GetIndices< typename std::decay< Tuple >::type >::Ix > auto pass( Fn &&fn, Tuple &&tuple ) -> decltype( _Pass< Ix >::template call< Fn, Tuple >( std::forward< Fn >( fn ), std::forward< Tuple >( tuple ) ) ) { return _Pass< Ix >::template call< Fn, Tuple >( std::forward< Fn >( fn ), std::forward< Tuple >( tuple ) ); } template< typename T, typename Fn, typename Tuple, typename UIx = typename GetIndices< typename std::decay< Tuple >::type >::Ix > auto pass( T &&obj, Fn &&method, Tuple &&tuple ) -> decltype( pass( bindThis( std::forward< T >( obj ), std::forward< Fn >( method ) ), std::forward< Tuple >( tuple ) ) ) { return pass( bindThis( std::forward< T >( obj ), std::forward< Fn >( method ) ), std::forward< Tuple >( tuple ) ); } template< int... ix, typename... Args > auto passIx( Args &&...args ) -> decltype( pass< Args..., Indices< ix... > >( std::forward< Args >( args )... ) ) { return pass< Args..., Indices< ix... > >( std::forward< Args >( args )... ); } } } namespace brick_test { namespace tuple { using namespace brick::tuple; struct Obj { void m1( int x ) { ASSERT_EQ( x, 42 ); } int m2( int x, int y ) { return x + y; } int m3( int x ) { return x + z; } int z; }; struct Tuple { TEST(call_empty) { std::tuple<> t; bool called = false; pass( [&] { called = true; }, t ); ASSERT( called ); } TEST(call_single) { std::tuple< int > t( 42 ); bool called = false; pass( [&]( int x ) { called = true; ASSERT_EQ( x, 42 ); }, t ); ASSERT( called ); } TEST(call_multi) { bool called = false; std::tuple< int, bool &, bool * > t( 42, called, &called ); pass( [&]( int x, bool &cr, bool *cp ) { called = true; ASSERT( cr ); ASSERT( *cp ); ASSERT( &cr == cp ); ASSERT( &called == cp ); ASSERT_EQ( x, 42 ); }, t ); ASSERT( called ); } TEST(call_lvalue) { std::tuple< int > t( 0 ); pass( []( int &x ) { x = 42; }, t ); ASSERT_EQ( std::get< 0 >( t ), 42 ); pass( []( const int &x ) { ASSERT_EQ( x, 42 ); }, t ); // pass( []( int &&x ) { }, t ); // this should not compile } TEST(call_rvalue) { pass( []{ }, std::tuple<>() ); pass( []( int x ) { ASSERT_EQ( x, 42 ); }, std::tuple< int >( 42 ) ); pass( []( int &&x ) { ASSERT_EQ( x, 42 ); }, std::tuple< int >( 42 ) ); // pass( []( int &x ) { ASSERT_EQ( x, 42 ); }, std::tuple< int >( 42 ) ); // this should not compile int x = 0; pass( []( int &x ) { x = 42; }, std::tuple< int & >( x ) ); ASSERT_EQ( x, 42 ); std::tuple< int, int > t( 40, 2 ); x = pass( []( int x, int &&y ) { return x + y; }, std::move( t ) ); ASSERT_EQ( x, 42 ); } TEST(retval) { ASSERT_EQ( pass( []( int x ) { return x + 2; }, std::tuple< int >( 40 ) ), 42 ); int z = pass( []( int x, int y ) { return x + y; }, std::tuple< int, int >( 40, 2 ) ); ASSERT_EQ( z, 42 ); } TEST(bind_this) { Obj o; o.z = 40; bindThis( o, &Obj::m1 )( 42 ); ASSERT_EQ( bindThis( o, &Obj::m3 )( 2 ), 42 ); } TEST(obj_bind) { Obj o; o.z = 40; pass( bindThis( o, &Obj::m1 ), std::tuple< int >( 42 ) ); int r = pass( bindThis( o, &Obj::m2 ), std::tuple< int, int >( 40, 2 ) ); ASSERT_EQ( r, 42 ); r = pass( bindThis( o, &Obj::m3 ), std::tuple< int >( 2 ) ); ASSERT_EQ( r, 42 ); o.z = 2; r = pass( bindThis( o, &Obj::m3 ), std::tuple< int >( 40 ) ); ASSERT_EQ( r, 42 ); } TEST(obj) { Obj o; o.z = 40; pass( o, &Obj::m1, std::tuple< int >( 42 ) ); int r = pass( o, &Obj::m2, std::tuple< int, int >( 40, 2 ) ); ASSERT_EQ( r, 42 ); r = pass( o, &Obj::m3, std::tuple< int >( 2 ) ); ASSERT_EQ( r, 42 ); o.z = 2; r = pass( o, &Obj::m3, std::tuple< int >( 40 ) ); ASSERT_EQ( r, 42 ); } TEST(objptr) { Obj o; o.z = 40; pass( &o, &Obj::m1, std::tuple< int >( 42 ) ); int r = pass( &o, &Obj::m2, std::tuple< int, int >( 40, 2 ) ); ASSERT_EQ( r, 42 ); r = pass( &o, &Obj::m3, std::tuple< int >( 2 ) ); ASSERT_EQ( r, 42 ); o.z = 2; r = pass( &o, &Obj::m3, std::tuple< int >( 40 ) ); ASSERT_EQ( r, 42 ); } TEST(pass_ix) { std::tuple< int, long, bool, int, int > t( 42, 1024l * 1024l * 1024l * 1024l, true, 40, 2 ); passIx< 0 >( []( int x ) { ASSERT_EQ( x, 42 ); }, t ); passIx< 1 >( []( long x ) { ASSERT_EQ( x, 1024l * 1024l * 1024l * 1024l ); }, t ); passIx< 2, 0 >( []( bool b, int x ) { ASSERT( b ); ASSERT_EQ( x, 42 ); }, t ); int r = passIx< 3, 4 >( []( int x, int y ) { return x + y; }, t ); ASSERT_EQ( r, 42 ); } struct CheckConst { template< typename T > void operator()( T & ) { static_assert( std::is_const< T >::value, "const got lost" ); } }; TEST(call_const) { const std::tuple< int > t( 42 ); pass( []( const int &x ) { ASSERT_EQ( x, 42 ); }, t ); pass( []( int x ) { ASSERT_EQ( x, 42 ); }, t ); pass( CheckConst(), t ); std::tuple< const int > tc( 42 ); pass( CheckConst(), tc ); int x = 42; pass( CheckConst(), std::tuple< const int & >( x ) ); } }; } } #endif // BRICK_TUPLE_H // vim: syntax=cpp tabstop=4 shiftwidth=4 expandtab