Search code examples
c++variadic-templatesstdtuple

How to remove const ref modifiers for each element in typename... T


I just started to replace some of my old templates with variadic versions to avoid code duplication (or ugly macros) for overloads with variable number of arguments.

One Issue I run into and did not solved yet is: How to remove const& from any type that is part of a 'mixed typename...' ?

Here is my example:

    #include <tuple>

    // old version to be replaced
    namespace old_version
    {
        template<typename T> struct struct_const_deref { typedef T Type; };
        template<typename T> struct struct_const_deref < const T& > { typedef T Type; };

        template < typename F, typename T1 >
        void exec(F* pObj, void(F::*pFct)(T1))
        {
            typename struct_const_deref<T1>::Type t1;

            // some code that fills t1

            (pObj->*pFct)(t1);
        }

        template < typename F, typename T1 , typename T2 >
        void exec(F* pObj, void(F::*pFct)(T1, T2))
        {
            typename struct_const_deref<T1>::Type t1;
            typename struct_const_deref<T2>::Type t2;

            // some code that fills t1, t2

            (pObj->*pFct)(t1, t2);
        }
    }

    // new variadic version
    namespace variadic 
    {
        template< typename... Args > struct struct_const_deref_tuple { typedef std::tuple< Args... > Tuple; };
        template< typename... Args > struct struct_const_deref_tuple < const Args&... > { typedef std::tuple< Args... > Tuple; };

        template < typename F, typename... Args >
        void exec(F* pObj, void(F::*pFct)(Args... args))
        {
            typename struct_const_deref_tuple< Args... >::Tuple tN;

            // some code that fills tN

            // some helper template that 'extracts' the tuple and calls (pObj->*pFct)(ExtractedArgs...)
        }
    }

    struct Test
    {
        void foo(int i) {}
        void bar(const float& f, const int& i) {}
        void buu(const float& f, int i) {}
    };



    int main(int argc, char* argv[])
    {
        Test t;

        old_version::exec(&t, &Test::foo); // ok
        old_version::exec(&t, &Test::bar); // ok
        old_version::exec(&t, &Test::buu); // ok

        variadic::exec(&t, &Test::foo); // ok
        variadic::exec(&t, &Test::bar); // ok
        variadic::exec(&t, &Test::buu); // fails (the struct_const_deref_tuple does not 'catch' my case; 'tN' will be std::tuple<const float&, int>

        return 0;
    }

Since the old solution needed a version for each number of arguments I would like to replace it with the variadic solution.

Unfortunately the variadic version fails on

Test::buu

because tN becomes

std::tuple<const float&, int> 

but it obviously needs to be

std::tuple<float, int> 

for me to work.

Any ideas welcome :)


Solution

  • The essential problem is pack expansion. Also, you can use std::decay(c++11) or std::decay_t (c++14) from type_traits for simplicity. The following code should compile with c++14.

    #include <tuple>
    #include <type_traits>
    
    // new variadic version
    namespace variadic
    {
        template < typename F, typename... Args >
        void exec(F* pObj, void(F::*pFct)(Args... args))
        {
            std::tuple<std::decay_t<Args>...> tN;
    
            // some code that fills tN
    
            // some helper template that 'extracts' the tuple and calls (pObj->*pFct)(ExtractedArgs...)
        }
    }
    
    struct Test
    {
        void foo(int i) {}
        void bar(const float& f, const int& i) {}
        void buu(const float& f, int i) {}
    };
    
    
    
    int main(int argc, char* argv[])
    {
        Test t;
    
        variadic::exec(&t, &Test::foo); // ok
        variadic::exec(&t, &Test::bar); // ok
        variadic::exec(&t, &Test::buu); // ok
    
        return 0;
    }