调用可变参数函数模板时传递花括号初始化列表参数出现问题

15

考虑这个函数模板:

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

2
thisthisthis - Piotr Skotnicki
1
@PiotrSkotnicki 看起来对于OP的需求(C++11)很有前途。那么为什么你不把它们粘贴成答案呢?(只是好奇 - JeJo
1
由于{1,'2',3.0}中包含不同类型的元素,因此无法将其推断为std :: initializer_list或C风格数组;也无法将其推断为std :: tuple <T,char,double>,因为{1,'2',3.0}本身不是std :: tuple。我想你必须使用K,或者显式调用foo()的类型(如foo<int>({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推断。 - max66
1
@n.m.那么像这里提供足够数量的重载有什么问题吗? - Piotr Skotnicki
1
在需要额外的一对括号之前,我的第三个假设是:foo(1,'2',3.0, {{4,'5',6.0}, {7,'8',9.0}})。因此,第一个1被推断为int,接下来的三元组被推断为std::tuple<int, char, double>const [2] - max66
显示剩余8条评论
3个回答

9

生成一个重载的构造函数集:

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

演示


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

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

演示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});
}

演示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


引人入胜的第一个解决方案 - max66

4

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

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限定符得到std::initializer_­list<P'>P'[N],其中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
 }

很遗憾你不能使用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
 }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接