【问题标题】:Problem with calling a variadic function template when passing brace initialiser list arguments传递大括号初始化器列表参数时调用可变参数函数模板的问题
【发布时间】:2019-05-10 12:54:28
【问题描述】:

考虑这个函数模板:

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 兼容的解决方案。

【问题讨论】:

  • thisthisthis
  • @PiotrSkotnicki 这看起来很有希望满足 OP 的要求(C++11)。那么为什么不将它们粘贴为答案呢? (只是好奇
  • 由于类型不同,{1, '2', 3.0}不能推导出为std::initializer_list或C风格的数组;并且不能推断为std::tuple&lt;T, char, double&gt;,因为{1, 2, 3.0} 本身不是std::tuple。我想你必须使用K,或者显式调用foo()(所以foo&lt;int&gt;( {1,'2',3.0}, {4,'5',6.0}, {7,'8',9.0} );)或者避免使用大括号,至少对于第一个三元组(所以foo(1,'2',3.0, {4,'5',6.0}, {7,'8',9.0}))允许T 扣除。跨度>
  • @n.m.那么提供足够数量的重载(如here)有什么问题?
  • 我在 require 之前的第三个假设和另外几个大括号:foo(1,'2',3.0, {{4,'5',6.0}, {7,'8',9.0}})。所以第一个1被推导出为int,接下来的三元组为std::tuple&lt;int, char, double&gt;const [2]

标签: c++ c++11 variadic-templates list-initialization


【解决方案1】:

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

#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


作为数组传递并推断其大小(需要额外的一对括号):

#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 作为构造函数参数(跳过额外的括号):

#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

【讨论】:

  • 有趣的第一个解决方案
【解决方案2】:

{} 不是表达式因此没有类型,参数推导关注类型,当用于执行参数推导的参数是模板函数初始化列表时要特别注意参数必须有具体的形式,否则参数是非推导上下文。一个更简单的例子是:

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-qualifiers 对于某些 P'N 给出 std::initializer_­list&lt;P'&gt;P'[N] 并且参数是非空初始化列表 ([dcl.init.list]),则而是对初始化器列表的每个元素执行推导,将P' 作为函数模板参数类型,将初始化器元素作为其参数,在P'[N] 的情况下,如果N 是非类型模板参数, N 是从初始化列表的长度推导出来的。否则,初始化列表参数会导致参数被视为非推导上下文

[temp.deduct.type]/5

未推断的上下文是:

(5.6) 一个函数形参,其关联实参是一个初始化列表 ([dcl.init.list]) 但该形参没有指定从初始化列表中推导的类型 ([temp.deduct.调用])。

当你:

  • 显式提供模板参数,它可以工作......没有什么可以推断的
  • 将参数指定为K{1},它可以工作...参数不再是初始化器列表,而是具有类型的表达式。

【讨论】:

    【解决方案3】:

    我不能为此使用 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
     }
    

    很遗憾你不能使用 C++17,因为你可以使用可变参数 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
     }
    

    【讨论】:

      猜你喜欢
      • 2013-12-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-05-15
      • 1970-01-01
      • 1970-01-01
      • 2014-10-20
      相关资源
      最近更新 更多