基本上问题可以用这个例子来概括:
template <typename ...Us>
void foo(Us...) { std::cout << "A\n"; }
template <typename ...Us>
void foo(Us..., int) { std::cout << "B\n"; }
int main(){
foo(1,2,3);
}
这会调用第一个foo
(打印A
)。如何让它拨打第二个foo
?
如果这使用了非变量模板,或者如果&#34; int&#34;是第一个参数,然后重载规则将调用正确的函数。也就是说,特定类型(int
)是比模板更好的匹配,因此它会调用第二个foo
。但显然,变量模板的情况并非如此?当variadic模板不是最后一个参数时,有没有办法重载它?
答案 0 :(得分:6)
当参数包没有出现在参数声明的最后时,它是一个非推导的上下文。非推断的上下文意味着必须明确给出模板参数。这就是为什么foo
#1更好的过载。您可以通过提供显式参数(foo<int,int>(1,2,3)
)来强制执行第二次重载调用,或者如您所述,将int
移到前面。
为了清楚起见,可以使用可变参数模板重载函数,但是当它们不作为最后一个参数出现时,它们不能被推导出来,当显式参数为时,它们会自动取消它们作为候选者的资格。不提供。提供它们时,模板参数将替换为其提供的类型,并且生成的非模板函数是重载解析的候选函数。
要回答您的问题,您可以将所有参数放入元组中并挑选最后一个并测试该元组。然后基于简单的is_same
检查传递过载:
template<class...Us>
void foo_impl(true_type,Us...); // last argument is int
template<class...Us>
void foo_impl(false_type,Us...); // last argument non-int
template<class...Us>
void foo( Us&&...us ) {
using tuple=tuple<Us...>;
using last=decltype(get<sizeof...(Us)-1>(declval<tuple>()));
foo_impl(is_same<decay_t<last>,int>{}, forward<Us>(us)...);
}
答案 1 :(得分:5)
您可以使用SFINAE并提供一些std::tuple
功能帮助(支持c ++ 11的代码):
#include <type_traits>
#include <tuple>
#include <iostream>
template <typename ...Us>
typename std::enable_if<!std::is_same<typename std::tuple_element<sizeof...(Us) - 1, std::tuple<Us...>>::type, int>::value>::type foo(Us...) {
std::cout << "A\n";
}
template <typename ...Us>
typename std::enable_if<std::is_same<typename std::tuple_element<sizeof...(Us) - 1, std::tuple<Us...>>::type, int>::value>::type foo(Us...) {
std::cout << "B\n";
}
int main(){
foo(1,2,3);
}
输出:
乙
如果您希望它测试pack中的某个其他参数是否属于给定类型,只需将std::tuple_element
第一个参数更改为所需的索引值。
如果您还想利用参数包中的其他参数,例如通过递归调用,然后你遇到更大麻烦... c ++ 11没有创建索引包的功能。您需要自己实现该功能
#include <tuple>
#include <utility>
#include <iostream>
#include <initializer_list>
template <class T, T... Vs>
struct integer_sequence { };
template <class T, class, class, class = integer_sequence<T>, class = integer_sequence<T, 0>, class = void>
struct make_integer_sequence_impl;
template <class T, T ICV1, T... Res, T... Pow>
struct make_integer_sequence_impl<T, std::integral_constant<T, ICV1>, std::integral_constant<T, 0>, integer_sequence<T, Res...>, integer_sequence<T, Pow...>, typename std::enable_if<(ICV1 > 0)>::type>: make_integer_sequence_impl<T, std::integral_constant<T, ICV1/2>, std::integral_constant<T, ICV1%2>, integer_sequence<T, Res...>, integer_sequence<T, Pow..., (Pow + sizeof...(Pow))...>> { };
template <class T, T ICV1, T... Res, T... Pow>
struct make_integer_sequence_impl<T, std::integral_constant<T, ICV1>, std::integral_constant<T, 1>, integer_sequence<T, Res...>, integer_sequence<T, Pow...>, void>: make_integer_sequence_impl<T, std::integral_constant<T, ICV1/2>, std::integral_constant<T, ICV1%2>, integer_sequence<T, Pow..., (Res + sizeof...(Pow))...>, integer_sequence<T, Pow..., (Pow + sizeof...(Pow))...>> { };
template <class T, class Res, class Pow>
struct make_integer_sequence_impl<T, std::integral_constant<T, 0>, std::integral_constant<T, 0>, Res, Pow, void> {
using type = Res;
};
template <class T, T V>
using make_integer_sequence = typename make_integer_sequence_impl<T, std::integral_constant<T, V/2>, std::integral_constant<T, V%2>>::type;
template <size_t V>
using make_index_sequence = make_integer_sequence<size_t, V>;
template <size_t... V>
using index_sequence = integer_sequence<size_t, V...>;
void foo() { }
template <typename ...Us>
void foo(Us... us);
template <typename ...Us, std::size_t... Is>
typename std::enable_if<!std::is_same<typename std::tuple_element<sizeof...(Us) - 1, std::tuple<Us...>>::type, int>::value>::type foo_impl(index_sequence<Is...>, Us... us) {
std::cout << std::get<sizeof...(Us) - 1>(std::forward_as_tuple(us...)) << "A\n";
foo(std::get<Is>(std::forward_as_tuple(us...))...);
}
template <typename ...Us, std::size_t... Is>
typename std::enable_if<std::is_same<typename std::tuple_element<sizeof...(Us) - 1, std::tuple<Us...>>::type, int>::value>::type foo_impl(index_sequence<Is...>, Us... us) {
std::cout << std::get<sizeof...(Us) - 1>(std::forward_as_tuple(us...)) << "B\n";
foo(std::get<Is>(std::forward_as_tuple(us...))...);
}
template <typename ...Us>
void foo(Us... us) {
foo_impl(make_index_sequence<sizeof...(Us) - 1>{}, std::forward<Us>(us)...);
}
int main(){
foo(1,2,3);
}
或重新考虑参数访问模式:
#include <iostream>
void foo() { }
template <typename Other, typename ...Us>
void foo(Other first, Us... rest) {
foo(rest...);
std::cout << first << "A\n";
}
template <typename ...Us>
void foo(int first, Us... rest) {
foo(rest...);
std::cout << first << "B\n";
}
int main(){
foo(1,2,3);
}
答案 2 :(得分:2)
其他答案在编制行李方面非常沉重(std::tuple
是一个非常复杂的模板),所以,让我向您展示一种直截了当的方式。
首先,我已经解决了这个问题,我需要保留参数的顺序,因为它们是在其他地方生成的,并且通常更改参数的顺序是繁忙的工作。
重载功能非常强大,但它可以更加自动地减少参数类型;这就是为什么没有模板函数的部分特化的一个原因。出于用户界面的目的,您必须使其具有一个包装器,以确定类型是否以int
结尾。因此,你需要一个类型特征:
template<typename T, typename... Ts>
struct LastIsInt{
constexpr static bool value = LastIsInt<Ts...>::value;
};
template<typename T>
struct LastIsInt<T> {
constexpr static bool value = false;
};
template<>
struct LastIsInt<int> {
constexpr static bool value = true;
};
您对空类型参数包的选择。
然后,你可以:
template<typename... Args> void foo_of_int(Args &&...);
template<typename... Args> void foo_of_non_int(Args &&...);
template<typename... Args>
void foo(Args &&...args) {
if(LastIsInt<Args...>::value) {
foo_of_int(std::forward<Args>(args)...);
} else {
foo_of_non_int(std::forward<Args>(args)...);
}
}