传递大括号初始化列表参数时调用可变函数模板的问题
Problem with calling a variadic function template when passing brace initialiser list arguments
考虑这个函数模板:
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 兼容的解决方案是首选。
{}
不是表达式因此没有类型,参数推导与类型有关,当用于执行参数推导的参数是 初始化器列表时要特别小心=38=] 模板函数参数必须有特定形式,否则参数是非推导上下文。一个更简单的例子是这样的:
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
涵盖了这一点
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
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]).
当你:
- 显式提供模板参数,有效...没有什么可推断的
- 将参数指定为
K{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});
}
生成一组重载的函数调用运算符:
#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});
}
创建(或使用预处理器宏生成)一组将参数转发给单个实现的重载:
#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});
}
作为数组传递并推断其大小(需要额外的一对括号):
#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 ~~~~~^
}
使用 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} };
}
I cannot use C++17 for this. A C++11-compatible solution is much preferred.
使用 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_sequence
和 std::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
}
考虑这个函数模板:
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 兼容的解决方案是首选。
{}
不是表达式因此没有类型,参数推导与类型有关,当用于执行参数推导的参数是 初始化器列表时要特别小心=38=] 模板函数参数必须有特定形式,否则参数是非推导上下文。一个更简单的例子是这样的:
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
涵盖了这一点
If removing references and cv-qualifiers from P gives
std::initializer_list<P'>
orP'[N]
for someP'
andN
and the argument is a non-empty initializer list ([dcl.init.list]), then deduction is performed instead for each element of the initializer list, takingP'
as a function template parameter type and the initializer element as its argument, and in theP'[N]
case, ifN
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
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]).
当你:
- 显式提供模板参数,有效...没有什么可推断的
- 将参数指定为
K{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});
}
生成一组重载的函数调用运算符:
#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});
}
创建(或使用预处理器宏生成)一组将参数转发给单个实现的重载:
#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});
}
作为数组传递并推断其大小(需要额外的一对括号):
#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 ~~~~~^
}
使用 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} };
}
I cannot use C++17 for this. A C++11-compatible solution is much preferred.
使用 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_sequence
和 std::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
}