Search code examples
c++c++11templatesvariadic-templates

How do I access variadic members?


template<typename... Types>
struct Foo;

template<typename T , typename... Types>
struct Foo<T, Types ...> : public Foo<Types ...>
{
    Foo( T member , Types ... others ) : Foo<Types ...>( others... ), m_member( member )
    {
    }

    T   m_member;
};

template<typename T>
struct Foo<T>
{
    Foo( T member ) : m_member( member )
    {
    }

    T   m_member;
};

int main()
{
  Foo<char,int,bool,double> f( 'a' , 42 , true , 1.234 );
}

I found this code somewhere on SO and I am wondering if it's completely useless? It seems to me that all members are called m_member so how would I access them?

If I do cout << f.m_member; it will print 'a', but I see no way to access the other members.


Solution

  • In your current implementation each derived Foo class shadows its parent's m_member. It is up to you how to implement a logic of accessing each field (via indexing, types, other).

    One possibility is to access them by an overloaded templated member function taking a type or an index (reversed for simplicity):

    #include <type_traits>
    #include <cstddef>
    
    template <typename... Types>
    struct Foo;
    
    template <typename T, typename... Types>
    struct Foo<T, Types...> : Foo<Types...>
    {
        // bring get() member functions from parent class into current scope
        using Foo<Types...>::get;
    
        Foo(T member, Types... others) : Foo<Types...>{others...}, m_member{member} {}
    
        template <typename U>
        auto get(T* = nullptr)
            -> typename std::enable_if<std::is_same<U, T>::value, T&>::type
        {
            return m_member;
        }
    
        template <std::size_t N>
        auto get(T* = nullptr)
            -> typename std::enable_if<N == sizeof...(Types), T&>::type
        {
            return m_member;
        }
    
    private:
        T m_member;
    };
    
    template <typename T>
    struct Foo<T>
    {
        Foo(T member) : m_member{member} {}
    
        template <typename U>
        auto get(T* = nullptr)
            -> typename std::enable_if<std::is_same<U, T>::value, T&>::type
        {
            return m_member;
        }
    
        template <std::size_t N>
        auto get(T* = nullptr)
            -> typename std::enable_if<N == 0, T&>::type
        {
            return m_member;
        }
    
    private:
        T m_member;
    };
    

    Tests:

    Foo<char, int, bool, double> a{ 'a', 42, true, 1.234 };
    
    assert('a' == a.get<char>());
    assert(42 == a.get<int>());
    
    assert(true == a.get<1>());
    assert(42 == a.get<2>());
    
    a.get<char>() = 'b';
    assert('b' == a.get<3>());
    

    DEMO

    For other implementations that provide an access to members see std::tuple<...> with its std::get<N>().

    A canonical implementation looks as below:

    #include <type_traits>
    #include <cstddef>
    
    template <typename... Types>
    struct Foo;
    
    template <typename T, typename... Types>
    struct Foo<T, Types...> : Foo<Types...>
    {
        Foo(T member, Types... others) : Foo<Types...>{others...}, m_member{member} {}
        T m_member;
    };
    
    template <typename T>
    struct Foo<T>
    {
        Foo(T member) : m_member{member} {}
        T m_member;
    };
    
    template <std::size_t N, typename T>
    struct element;
    
    template <typename T, typename... Types>
    struct element<0, Foo<T, Types...>> 
    {
        using type = T;
    };
    
    template <std::size_t N, typename T, typename... Types>
    struct element<N, Foo<T, Types...>> 
    {
        using type = typename element<N - 1, Foo<Types...>>::type;
    };
    
    template <std::size_t N, typename T, typename... Types>
    auto get(Foo<T, Types...>& f)
        -> typename std::enable_if<N == 0, T&>::type
    {
        return f.m_member;
    }
    
    template <std::size_t N, typename T, typename... Types>
    auto get(Foo<T, Types...>& f)
        -> typename std::enable_if<N != 0
                                  , typename element<N, Foo<T, Types...>>::type&
                                  >::type
    {
        Foo<Types...>& p = f;
        return get<N - 1>(p);
    }
    
    template <typename U, typename T, typename... Types>
    auto get(Foo<T, Types...>& f)
        -> typename std::enable_if<std::is_same<T, U>::value, T&>::type
    {
        return f.m_member;
    }
    
    template <typename U, typename T, typename... Types>
    auto get(Foo<T, Types...>& f)
        -> typename std::enable_if<!std::is_same<T, U>::value, U&>::type
    {
        Foo<Types...>& p = f;
        return get<U>(p);
    }
    

    Tests:

    Foo<char, int, bool, double> a{ 'a', 42, true, 1.234 };
    
    assert(true == get<2>(a));
    assert(42 == get<int>(a));
    
    get<char>(a) = 'b';
    assert('b' == get<0>(a));
    

    DEMO 2