Search code examples
c++templatesrecursionvariadic

Variadic template recursive types passing


I found almost satisfying solution for my problem here(2nd answer), but I cant use code written this way in another compilation unit, since putting code in header file causes linker to complain about multiple function definition and putting only declaration in header file causes linker undefined reference problem.

Here is my code:

template <typename... types>
void foo();

template<>
void foo<>() {
    return;
}

template<typename type, typename... types>
void foohelper() {
    foo<types...>();
}

template <typename... types>
void foo() {
    foohelper<types...>();
}

int main() {
    foo<int, int>();
}

And here is what I want to achieve:

class A {
public:
    template<>
    void foo<>() {
        return;
    }

    template<typename parser, typename... parsers>
    void foohelper() {
        foo<parsers...>();
    }

    template <typename... parsers>
    void foo() {
        foohelper<parsers...>();
    }
};

int main() {
    A a;
    a.foo<int, int>();
}

But this causes following error during compilation:

explicit specialization 'void A::foo(void)' is not a specialization of a function template

Is there any simple solution for this?


Solution

  • no need for recursion. This is simpler:

    #include <iostream>
    #include <string>
    #include <typeinfo>
    
    class A {
    public:
    
        template<typename parser>
        void foohelper() {
            std::cout << "handled a " << typeid(parser).name() << std::endl;
            // do work here
        }
    
        template <typename... parsers>
        void foo() {
            using expand = int[];
            (void) expand { 0, (foohelper<parsers>(), 0)... };
        }
    };
    
    int main() {
        A a;
        a.foo<int, int, double, std::string>();
    }
    

    sample output:

    handled a i
    handled a i
    handled a d
    handled a NSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEE
    

    EDIT:

    In response to the non-conforming microsoft compiler requirement, here's another version that does not rely on an unsized array:

    #include <iostream>
    #include <string>
    #include <typeinfo>
    
    class A {
    public:
    
        template<typename parser>
        void foohelper() {
            std::cout << "handled a " << typeid(parser).name() << std::endl;
            // do work here
        }
    
        template <typename... parsers>
        void foo() {
            // c++ strictly does not allow 0-sized arrays.
            // so here we add a NOP just in case parsers is an empty type list
            using expand = int[1 + sizeof...(parsers)];
            (void) expand {
                (foohelper<void>(), 0),
                (foohelper<parsers>(), 0)...
            };
        }
    
    };
    
    // implement the NOP operation. Note specialisation is outside class definition.
    template<> void
    A::foohelper<void>() {}
    
    
    
    int main() {
        A a;
        a.foo<int, int, double, std::string>();
        a.foo<>();
    }
    

    EDIT 2:

    fuller example with prefix, postfix and inter-parser call-outs. Having written this much code, you'll probably start thinking, "Hey! I could implement a whole domain-specific language here!", and you'd be right.

    However, much more complexity than this will probably earn you the eternal hatred of your colleagues, so I would avoid going down that route.

    #include <iostream>
    #include <string>
    #include <typeinfo>
    
    class A {
    public:
    
        template<typename parser>
        void foohelper() {
            std::cout << "handled a " << typeid(parser).name();
            // do work here
        }
    
        void prepare()
        {
            std::cout << "starting parsers: ";
        }
    
        void separator()
        {
            std::cout << ", ";
        }
    
        void nothing()
        {
    
        }
    
        void done() {
            std::cout << " done!" << std::endl;
        }
    
        template <typename... parsers>
        void foo() {
            // c++ strictly does not allow 0-sized arrays.
            // so here we add a NOP just in case parsers is an empty type list
            bool between = false;
            using expand = int[2 + sizeof...(parsers)];
            (void) expand {
                (prepare(), 0),
                ((between ? separator() : nothing()), between = true, foohelper<parsers>(), 0)...,
                (done(), 0)
            };
        }
    
    };
    
    
    int main() {
        A a;
        a.foo<int, int, double, std::string>();
        a.foo<>();
    }
    

    sample output:

    starting parsers: handled a i, handled a i, handled a d, handled a NSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEE done!
    starting parsers:  done!