首页 文章

传递大括号列表参数时调用可变参数函数模板的问题

提问于
浏览
12

考虑这个功能模板:

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

此调用有效:

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

这个没有:

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

(gcc和clang都抱怨 foo 的论据太多了)

为什么第二次调用有问题?我可以重写 foo 的声明,以便第二次通话也被接受吗?

模板参数T仅用于实现可变参数 . 实际类型是已知且已修复的,只有参数的数量不同 . 在现实生活中,类型与 int, char, double 不同,这只是一个例子 .

我不能使用C 17 . 更优选C 11兼容的溶液 .

3 回答

  • 4

    生成一组重载的构造函数:

    #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


    生成一组重载的函数调用操作符:

    #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


    创建(或使用预处理器宏生成)一组重载,将参数转发给单个实现:

    #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


    作为数组传递并推断其大小(需要额外的一对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


    使用 std::initializer_list 作为构造函数参数(跳过额外的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

  • 8

    {} 不是表达式因此没有类型,参数推导关注类型,当用于执行参数推导的参数是初始化列表时,需要特别注意模板函数参数必须具有特定形式,否则参数是非 - 受到限制的背景 . 一个更简单的例子是:

    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
    

    这由[temp.deduc.call]/1涵盖

    如果从P中删除引用和cv限定符,则为某些P'和N提供std :: initializer_list <P'>或P'[N],并且该参数是非空的初始化列表([dcl.init.list]) ,然后对初始化列表的每个元素执行演绎,将P'作为函数模板参数类型,将初始化元素作为其参数,在P'[N]情况下,如果N是非类型模板参数,N是从初始化列表的长度推导出来的 . 否则,初始化列表参数会将参数视为非推导上下文

    [temp.deduct.type]/5

    非推导的上下文是:(5.6)一个函数参数,其关联参数是初始化列表([dcl.init.list]),但该参数没有指定从初始化列表中扣除的类型( [temp.deduct.call]) .

    当你:

    • 显式提供模板参数,它可以工作......没有什么可推断的

    • 将参数指定为 K{1} ,它有效...参数不再是初始化列表,是一个带有类型的表达式 .

  • 0

    我不能使用C 17 . 更优选C 11兼容的溶液 .

    使用C 11稍微复杂一点(没有 std::index_sequence ,没有 std::make_index_sequence )但是,如果你想保持元组的可变元使用......那就是...如果你真的想要一些东西

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

    如果你接受调用模板结构的静态方法,你可以定义一个递归继承自身的模板结构,并递归地定义一个

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

    哪里 K 是你的

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

    以下是完整的C 11编译示例

    #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
     }
    

    如果你可以使用C 14,你可以使用 std::make_index_sequencestd::index_sequence ,代码变得更好,恕我直言

    #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
     }
    

    它's a pity you can' t使用C 17,因为在你可以使用variadic unsing 并避免所有递归继承

    #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
     }
    

相关问题