【问题标题】:How to overload variadic templates when they're not the last argument当它们不是最后一个参数时如何重载可变参数模板
【发布时间】:2016-11-07 22:14:39
【问题描述】:

这个问题基本上可以用这个例子来概括:

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

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

【问题讨论】:

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


【解决方案1】:

当参数包没有出现在参数声明的最后时,它是一个非推导上下文。非推导上下文意味着必须明确给出模板参数。这就是为什么foo #1 是一个更好的重载。您可以通过提供显式参数 (foo&lt;int,int&gt;(1,2,3)) 来强制第二次重载调用,或者如您所说,将 int 移到前面。

为了清楚起见,你可以用可变参数模板重载一个函数,但是当它们没有作为最后一个参数出现时,它们不能被推导,这会在显式参数出现时自动取消它们作为候选者的资格不提供。当它们被提供时,模板参数被它们提供的类型替换,并且生成的 non-template 函数是重载决议的候选者。

要回答您的问题,您可以将所有参数放入一个元组中,然后挑选出最后一个并对其进行测试。然后基于简单的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)...);
}

【讨论】:

    【解决方案2】:

    您可以将 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);
    }
    

    输出:

    B

    如果您希望它测试包中的某些其他参数是否属于给定类型,只需将 std::tuple_element 第一个参数更改为所需的索引值。

    [live demo]


    如果您还希望使用其他参数形式的参数包,例如通过递归调用,那么你的麻烦就更大了...... 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);
    }
    

    [live demo]

    或重新考虑参数访问模式:

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

    [live demo]

    【讨论】:

    • 请注意,就编译时内存和时间而言,tuple 是一种重量级的方法。但是已经写好了,所以……
    • @Yakk 是的,它总是困扰着我……标准中应该有一些轻量级的包……
    【解决方案3】:

    其他答案在编译包袱方面非常繁重(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)...);
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2012-08-07
      • 2018-05-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-01
      • 2014-10-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多