当前位置: 首页 > 知识库问答 >
问题:

当可变模板不是最后一个参数时,如何重载它们

阮炯
2023-03-14

基本上,这个问题可以用这个例子来总结:

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)。如何让它调用第二个<code>foo</code>?

如果这使用了非可变参数模板,或者“int”是第一个参数,则重载规则将调用正确的函数。也就是说,特定类型(int)比模板更匹配,因此它将调用第二个foo。但显然,可变参数模板不是这种情况吗?有没有办法在可变参数模板不是最后一个参数时重载它?

共有3个答案

周超英
2023-03-14

其他答案在编译包袱方面非常繁重(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)...);
    }
}
高弘光
2023-03-14
匿名用户

您可以将SFINAE与一些std::tuple功能帮助(支持c11的代码)一起使用:

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

输出:

B.

如果您希望它测试包中的其他参数是否属于给定类型,只需将< code>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);
}

[现场演示]

孔鸿远
2023-03-14

当参数包没有在参数声明中最后出现时,它是一个非推导上下文。非推导上下文意味着必须显式给出模板参数。这就是为什么foo#1是更好的重载。您可以通过提供显式参数(foo

为了说明问题,您可以用可变模板重载一个函数,但是当它们没有作为最后一个参数出现时,它们就无法被推导出来,这在没有提供显式参数时会自动取消它们的候选资格。当提供模板参数时,模板参数将被它们所提供的类型替换,并且产生的非模板函数是重载决策的候选函数。

要回答您的问题,您可以将所有参数放入一个元组并选择最后一个并测试该元组。然后根据简单的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)...);
}

 类似资料:
  • 这是在参数是重载函数时重载解析如何工作中提到的更复杂的问题? 下面的代码编译起来没有任何问题: 模板参数推导似乎不是一项具有挑战性的任务-只有一个函数接受两个参数。但是,取消注释的模板重载(仍然只有一个参数)会无缘无故地破坏编译。gcc 5. x/6. x和clang 3.9的编译都失败了。 它可以用重载解析/模板参数推导规则来解释,还是应该在这些编译器中被限定为缺陷?

  • 我希望打印函数根据“值”的类型来做不同的事情。

  • 受这个答案的启发,我生成了这段代码,其输出取决于编译器: 如果使用 GCC 11 编译,调用

  • 考虑以下代码: 我显式指定类型是因为(https://stackoverflow.com/a/40476083): 当参数包没有最后出现在参数声明中时,它是一个非推断上下文。非推断上下文意味着必须显式给出模板参数。 MSVC成功编译,而gcc和clang都拒绝代码: 现在让我们做一个小小的改变-我们从本地中删除,从而使模板参数包变为空: 这一次,三个编译器都认为代码不正确。使用http://rex

  • 在C++11之前,类模板和函数模板只能含有固定数量的模板参数。C++11增强了模板功能,允许模板定义中包含0到任意个模板参数,这就是可变参数模板。可变参数模板的加入使得C++11的功能变得更加强大,而由此也带来了许多神奇的用法。 可变参数模板 可变参数模板和普通模板的语义是一样的,只是写法上稍有区别,声明可变参数模板时需要在typename或class后面带上省略号...: template<ty

  • 要解决的问题: 怎么创建一个拥有1个、2个或者更多的初始化器的类? 怎么避免创建一个实例而只拷贝部分的结果? 怎么创建一个元组? 最后的问题是关键所在:考虑一下元组!如果你能创建并且访问一般的元组,那么剩下的问题也将迎刃而解。 这里有一个例子(摘自“可变参数模板简述(A brief introduction to Variadic templates)”(参见参考)),要构建一个广义的、类型安全的