Search code examples
c++c++11variadic-templateslist-initialization

Problem with calling a variadic function template when passing brace initialiser list arguments


Consider this function template:

template <class... T>
void foo (std::tuple<T, char, double> ... x);

This invocation works:

using K = std::tuple<int, char, double>;
foo ( K{1,'2',3.0}, K{4,'5',6.0}, K{7,'8',9.0} );

This one doesn't:

foo ( {1,'2',3.0}, {4,'5',6.0}, {7,'8',9.0} );

(gcc and clang both complain about too many arguments for foo)

Why is the second call a problem? Can I rewrite the declaration of foo so that the second call is also accepted?

Thee template parameter T is only used to implement variadicity. The actual type is known and fixed, only the number of arguments varies. In real life the types are different from int, char, double, this is just an example.

I cannot use C++17 for this. A C++11-compatible solution is much preferred.


Solution

  • Generate an overloaded set of constructors:

    #include <tuple>
    #include <cstddef>
    
    template <typename T, std::size_t M>
    using indexed = T;
    
    template <typename T, std::size_t M, std::size_t... Is>
    struct initializer : initializer<T, M, sizeof...(Is) + 1, Is...>
    {    
        using initializer<T, M, sizeof...(Is) + 1, Is...>::initializer;
    
        initializer(indexed<T, Is>... ts)
        {
            // ts is a pack of std::tuple<int, char, double>
        }
    };
    
    template <typename T, std::size_t M, std::size_t... Is>
    struct initializer<T, M, M, Is...> {};
    
    using foo = initializer<std::tuple<int, char, double>, 20>;
    //                                   tuples limit+1 ~~~^
    
    int main()
    {
        foo({1,'2',3.0});
        foo({1,'2',3.0}, {4,'5',6.0});
        foo({1,'2',3.0}, {4,'5',6.0}, {7,'8',9.0});
    }
    

    DEMO


    Generate an overloaded set of function call operators:

    #include <tuple>
    #include <cstddef>
    
    template <typename T, std::size_t M>
    using indexed = T;
    
    template <typename T, std::size_t M, std::size_t... Is>
    struct initializer : initializer<T, M, sizeof...(Is) + 1, Is...>
    {    
        using initializer<T, M, sizeof...(Is) + 1, Is...>::operator();
    
        int operator()(indexed<T, Is>... ts) const
        {            
            // ts is a pack of std::tuple<int, char, double>
            return 1;
        }
    };
    
    template <typename T, std::size_t M, std::size_t... Is>
    struct initializer<T, M, M, Is...>
    {
        int operator()() const { return 0; }
    };
    
    static constexpr initializer<std::tuple<int, char, double>, 20> foo = {};
    //                                        tuples limit+1 ~~~^
    
    int main()
    {    
        foo({1,'2',3.0});
        foo({1,'2',3.0}, {4,'5',6.0});
        foo({1,'2',3.0}, {4,'5',6.0}, {7,'8',9.0});
    }
    

    DEMO 2


    Create (or generate with preprocessor macros) a set of overloads that forward arguments to a single implementation:

    #include <array>
    #include <tuple>
    
    using K = std::tuple<int, char, double>;
    
    void foo(const std::array<K*, 5>& a)
    {
        // a is an array of at most 5 non-null std::tuple<int, char, double>*
    }
    
    void foo(K p0) { foo({&p0}); }
    void foo(K p0, K p1) { foo({&p0, &p1}); }
    void foo(K p0, K p1, K p2) { foo({&p0, &p1, &p2}); }
    void foo(K p0, K p1, K p2, K p3) { foo({&p0, &p1, &p2, &p3}); }
    void foo(K p0, K p1, K p2, K p3, K p4) { foo({&p0, &p1, &p2, &p3, &p4}); }
    
    int main()
    {
        foo({1,'2',3.0});
        foo({1,'2',3.0}, {4,'5',6.0});
        foo({1,'2',3.0}, {4,'5',6.0}, {7,'8',9.0});
    }
    

    DEMO 3


    Pass as an array and deduce its size (requires additional pair of parens):

    #include <tuple>
    #include <cstddef>
    
    template <std::size_t N>
    void foo(const std::tuple<int, char, double> (&a)[N])
    {
        // a is an array of exactly N std::tuple<int, char, double>
    }
    
    int main()
    {
        foo({{1,'2',3.0}, {4,'5',6.0}});
     //     ^~~~~~ extra parens ~~~~~^
    }
    

    DEMO 4


    Use an std::initializer_list as a constructor parameter (to skip extra parens):

    #include <tuple>
    #include <initializer_list>
    
    struct foo
    {
        foo(std::initializer_list<std::tuple<int, char, double>> li)
        {
            // li is an initializer list of std::tuple<int, char, double>
        }
    };
    
    int main()
    {
        foo{ {1,'2',3.0}, {4,'5',6.0} };
    }
    

    DEMO 5