Search code examples
c++listtemplatesvariadic-templates

How do I create a dynamic list in template metaprogramming?


I've been trying template metaprogramming and have had some trouble making a dynamic list. I've tried

#include <iostream>
template<int I>
struct Int {

};
template<class _Value, class ..._Others>
struct List {
    typedef _Value Value;
    typedef List<_Others...> Next;
};
template<class _Value>
struct List<_Value, void> {
    typedef _Value Value;
    typedef void Next;
};

template<class _List>
void PrintList() {
    std::cout << typename _List::Value::I << "\n";
    PrintList<typename _List::Next>();
};
template<>
void PrintList<void>() {};

int main() {
    PrintList<List<Int<1>, Int<2>, Int<3>>>();
}

But I get 2 compile errors that I can't figure out. The first is expected '(' before '<<' token on std::cout << typename _List::Value::I << "\n";. I can't figure out how to print out the int value (I've also tried (typename _List::Value)::I).

The second error is that I have the wrong number of template arguments at typedef List<_Others...> Next;. Shouldn't the first argument be capped into Value, and the rest Others?


Solution

  • You cannot access template parameters using :: syntax. The usual convention is to add type using for types and value constexpr static member for values.

    template<int I>
    struct Int {
       constexpr static auto value=I;
    };
    

    Then use std::cout << _List::Value::value << "\n";. It is not a type, so putting typename there was also incorrect.

    The second error is because you defined List having at least one argument, yet you recurse into List<_Others...> which is empty at the end. Even though you wanted to fix it with void, you forgot to add it to the list, try PrintList<List<Int<1>, Int<2>, Int<3>,void>>();

    But having a sentinel value is not necessary, see this:

    #include <iostream>
    template<int I>
    struct Int {
        static constexpr auto value=I;
    };
    //Define List as naturally having any number of arguments, even zero.
    template<class...Ts>
    struct List{};
    
    //Non-empty specialization.
    template<class Head, class...Tail>
    struct List<Head,Tail...>{
        using head = Head;
        using tail = List<Tail...>;
    };
    
    template<class _List>
    void PrintList() {
        std::cout <<  _List::head::value << "\n";
        PrintList<typename _List::tail>();
    };
    //Printing empty list.
    template<>
    void PrintList<List<>>() {};
    
    int main() {
        PrintList<List<Int<1>, Int<2>, Int<3>>>();
    }
    

    It is better to use using in C++, they are more readeable and can be templated.

    In C++17 you can use fold expressions:

    template<typename...Elements>
    void fold_print(List<Elements...>){
        ((std::cout<<Elements::value<<'\n'),...);
    }
    int main() {
        fold_print(List<Int<1>, Int<2>, Int<3>>{});
    }
    

    But functions can unpack only their arguments, so the syntax is slighlty different. Or it would require one more struct wrapper that unpacks them.