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

后端 未结 3 1767
一个人的身影
一个人的身影 2021-01-01 21:18

Consider this function template:

template 
void foo (std::tuple ... x);

This invocation works:

相关标签:
3条回答
  • 2021-01-01 21:26

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

    With C++11 is a little more complicated (no std::index_sequence, no std::make_index_sequence) but, if you want maintain the variadic use of tuples... that is... if you substantially want something as

    foo (std::tuple<int, char, double> ... ts)
    

    and if you accept to call a static method of a template struct, you can define a template struct that recursively inherit itself and, recursively, define a

    func ();
    func (K t0);
    func (K t0, K t1);
    func (K t0, K t1, K t2);
    

    where K is your

    using K = std::tuple<int, char, double>;
    

    The following is a full compiling C++11 example

    #include <tuple>
    #include <iostream>
    
    using K = std::tuple<int, char, double>;
    
    template <typename T, std::size_t>
    struct getTypeStruct
     { using type = T; };
    
    template <typename T, std::size_t N>
    using getType = typename getTypeStruct<T, N>::type;
    
    template <int ...>
    struct iList;
    
    template <std::size_t = 50u, std::size_t = 0u, typename = iList<>>
    struct foo;
    
    template <std::size_t Top, std::size_t N, int ... Is>
    struct foo<Top, N, iList<Is...>> : public foo<Top, N+1u, iList<0, Is...>>
     {
       using foo<Top, N+1u, iList<0, Is...>>::func;
    
       static void func (getType<K, Is> ... ts)
        { std::cout << sizeof...(ts) << std::endl; }
     };
    
    template <std::size_t Top, int ... Is>
    struct foo<Top, Top, iList<Is...>>
     {
       // fake func, for recursion ground case
       static void func ()
        { }
     };
    
    
    int main()
     {
       foo<>::func({1,'2',3.0}); // print 1
       foo<>::func({1,'2',3.0}, {4,'5',6.0}); // print 2
       foo<>::func({1,'2',3.0}, {4,'5',6.0}, {7,'8',9.0});  // print 3
     }
    

    If you can use C++14, you can use std::make_index_sequence and std::index_sequence and the code become a little better, IMHO

    #include <tuple>
    #include <iostream>
    #include <type_traits>
    
    using K = std::tuple<int, char, double>;
    
    template <std::size_t ... Is>
    constexpr auto getIndexSequence (std::index_sequence<Is...> is)
       -> decltype(is);
    
    template <std::size_t N>
    using IndSeqFrom = decltype(getIndexSequence(std::make_index_sequence<N>{}));
    
    template <typename T, std::size_t>
    struct getTypeStruct
     { using type = T; };
    
    template <typename T, std::size_t N>
    using getType = typename getTypeStruct<T, N>::type;
    
    template <std::size_t N = 50, typename = IndSeqFrom<N>>
    struct foo;
    
    template <std::size_t N, std::size_t ... Is>
    struct foo<N, std::index_sequence<Is...>> : public foo<N-1u>
     {
       using foo<N-1u>::func;
    
       static void func (getType<K, Is> ... ts)
        { std::cout << sizeof...(ts) << std::endl; }
     };
    
    template <>
    struct foo<0, std::index_sequence<>>
     {
       static void func ()
        { std::cout << "0" << std::endl; }
     };
    
    int main()
     {
       foo<>::func({1,'2',3.0});  // print 1
       foo<>::func({1,'2',3.0}, {4,'5',6.0});  // print 2
       foo<>::func({1,'2',3.0}, {4,'5',6.0}, {7,'8',9.0});  // print 3
     }
    

    It's a pity you can't use C++17 because in you could use variadic unsing and avoid at all recursive inheritance

    #include <tuple>
    #include <iostream>
    #include <type_traits>
    
    using K = std::tuple<int, char, double>;
    
    template <std::size_t ... Is>
    constexpr auto getIndexSequence (std::index_sequence<Is...> is)
       -> decltype(is);
    
    template <std::size_t N>
    using IndSeqFrom = decltype(getIndexSequence(std::make_index_sequence<N>{}));
    
    template <typename T, std::size_t>
    struct getTypeStruct
     { using type = T; };
    
    template <typename T, std::size_t N>
    using getType = typename getTypeStruct<T, N>::type;
    
    template <std::size_t N, typename = IndSeqFrom<N>>
    struct bar;
    
    template <std::size_t N, std::size_t ... Is>
    struct bar<N, std::index_sequence<Is...>>
     {
       static void func (getType<K, Is> ... ts)
        { std::cout << sizeof...(ts) << std::endl; }
     };
    
    template <std::size_t N = 50, typename = IndSeqFrom<N>>
    struct foo;
    
    template <std::size_t N, std::size_t ... Is>
    struct foo<N, std::index_sequence<Is...>> : public bar<Is>...
     { using bar<Is>::func...; };
    
    int main()
     {
       foo<>::func({1,'2',3.0});  // print 1
       foo<>::func({1,'2',3.0}, {4,'5',6.0});  // print 2
       foo<>::func({1,'2',3.0}, {4,'5',6.0}, {7,'8',9.0});  // print 3
     }
    
    0 讨论(0)
  • 2021-01-01 21:27

    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

    0 讨论(0)
  • 2021-01-01 21:41

    {} is not an expression hence don't have type, argument deduction is concerned about types, special care is taken when the argument used to perform argument deduction is an initializer list the template function parameter must have specifics forms, otherwise the parameter is a non-deduced context. A more simplistic example is this:

    template <class T> struct A { T r; };
    template <class T>
    void foo (A<T> x);
    
    using K = A<int>;
    foo({1}); // fail
    foo(K{1}); // compile
    

    This is covered by [temp.deduc.call]/1

    If removing references and cv-qualifiers from P gives std::initializer_­list<P'> or P'[N] for some P' and N and the argument is a non-empty initializer list ([dcl.init.list]), then deduction is performed instead for each element of the initializer list, taking P' as a function template parameter type and the initializer element as its argument, and in the P'[N] case, if N is a non-type template parameter, N is deduced from the length of the initializer list. Otherwise, an initializer list argument causes the parameter to be considered a non-deduced context

    and [temp.deduct.type]/5

    The non-deduced contexts are:

    (5.6) A function parameter for which the associated argument is an initializer list ([dcl.init.list]) but the parameter does not have a type for which deduction from an initializer list is specified ([temp.deduct.call]).

    When you:

    • explicitly provide template arguments, it works ... nothing to deduce
    • specify the argument as K{1}, it works ... the argument is not longer an initializer list, is an expression with type.
    0 讨论(0)
提交回复
热议问题