首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >C++11:可变同构非POD模板函数参数?

问C++11:可变同构非POD模板函数参数?
EN

Stack Overflow用户
提问于 2012-03-19 05:37:38
回答 4查看 1.2K关注 0票数 6

如何在C++11中编写一个带有数量可变的同构非POD函数参数的模板函数?

例如,假设我们想要为任何定义小于"operator<“的类型编写一个min函数,如下所示:

代码语言:javascript
复制
// pseduo-code...

template<class T...>
T min(T x1, T x2, ..., T xn)
{
    T lowest = x1;

    for (T x : {x2,...,xn})
       if (x < lowest)
           lowest = x;

    return lowest;
}

上面的是非法的C++11,你怎么合法地写它?

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2012-03-19 06:22:58

同构?只需使用 std::initializer_list.

代码语言:javascript
复制
template <typename T>
T min_impl(std::initializer_list<T> values)
{
    return *std::min_element(values.begin(), values.end());
}

...

return min_impl({8, 5, 4, 1, 6});

(正如@Jesse所指出的,这等同于标准库中的std::min。)

如果您不喜欢额外的大括号,可以创建一个可变模板,该模板转发给初始化器列表实现:

代码语言:javascript
复制
template <typename... T>
auto min(T&&... args) -> decltype(min_impl({std::forward<T>(args)...}))
{
    return min_impl({std::forward<T>(args)...});
}

...

return min(8, 5, 1, 4, 6);
票数 11
EN

Stack Overflow用户

发布于 2012-03-19 07:28:34

首先,可变模板不包含“单个类型的可变数量的参数”的方式。当您使用可变模板时,您会得到一个参数包,它是一组零个或多个参数,每个参数都可能具有唯一的类型:

代码语言:javascript
复制
template<typename... Ts> void foo(Ts... ts);

...标记只定义了这些参数包(和vararg函数,但这不是重点)的含义。所以你不能把它和非参数包一起使用:

代码语言:javascript
复制
template<typename T> void foo(T... t); // error

template<typename T> void foo(T t...); // error

其次,一旦有了参数包,就不能像使用基于范围的for循环那样遍历参数。相反,您必须以函数式风格编写算法,使用参数包扩展来“剥离”参数包中的参数。

代码语言:javascript
复制
// single argument base case
template<typename T>
void foo(T t) {
    std::cout << t;
}

template<typename T,typename... Us>
void foo(T t,Us... us) {
   foo(t) // handle first argument using single argument base case, foo(T t)
   foo(us...); // 'recurse' with one less argument, until the parameter pack
    // only has one argument, then overload resolution will select foo(T t)
}

尽管可变模板不能直接支持您想要的内容,但您可以使用enable_if并使用'SFINAE‘规则来施加此约束。首先,这里是一个没有约束的版本:

代码语言:javascript
复制
#include <type_traits>
#include <utility>

template<class T>
T min(T t) {
    return t;
}

template<class T,class... Us>
typename std::common_type<T,Us...>::type
min(T t,Us... us)
{
    auto lowest = min(us...);
    return t<lowest ? t : lowest;
}

int main() {
    min(1,2,3);
}

然后应用enable_if以确保所有类型都是相同的。

代码语言:javascript
复制
template<class T,class... Us>
typename std::enable_if<
    std::is_same<T,typename std::common_type<Us...>::type>::value,
    T>::type
min(T t,Us... us)
{
    auto lowest = min(us...);
    return t<lowest ? t : lowest;
}

根据is_same的说法,上述修改后的实现将防止在参数不完全相同的任何时候使用该函数。

如果没有必要,你最好不要使用这些技巧。像KennyTM建议的那样使用initializer_list可能是一个更好的想法。事实上,如果你真的实现了min和max,那么你可以省去你自己的麻烦,因为标准库已经包含了接受initializer_list的重载。

is_same<T,typename common_type<Us...>::type>是如何工作的?

因为min()只有一个参数版本,所以只有当有两个或更多参数时,才会选择可变版本。这意味着sizeof...(Us)至少是一个。在它恰好是1的情况下,common_type<Us...>返回该类型单个类型,并且is_same<T,common_type<Us...>>确保这两个类型是相同的。

min()的各种实现调用min(us...)。只要这个调用只在Us...中的所有类型都相同时才起作用,我们知道commont_type<Us...>告诉我们那个类型是什么,并且is_same<T,common_type<Us...>>确保T也是那个类型。

所以我们知道,只有当a和b是同一类型时,min(a,b)才能工作。我们知道min(c,a,b)调用min(a,b),所以只有当a和b是同一类型,并且c也是同一类型时,才能调用min(c,a,b)。min(d,c,a,b)调用min(c,a,b),因此我们知道只有当c、a和b都是相同类型,并且d也是相同类型时,才能调用b。等。

票数 4
EN

Stack Overflow用户

发布于 2012-03-19 06:04:00

这有点令人费解,但如果你想要异类参数,这就是你要得到的:

代码语言:javascript
复制
#include <iostream>
#include <type_traits>

template<typename F, typename T, typename Arg>
auto fold(F f, T&& t, Arg&& a) 
  -> decltype(f(std::forward<T>(t), std::forward<Arg>(a)))
{ return f(std::forward<T>(t), std::forward<Arg>(a)); }

template<typename F, typename T, typename Head, typename... Args>
auto fold(F f, T&& init, Head&& h, Args&&... args) 
  -> decltype(f(std::forward<T>(init), std::forward<Head>(h)))
{ 
  return fold(f, f(std::forward<T>(init), std::forward<Head>(h)), 
              std::forward<Args>(args)...); 
}

// polymorphic less
struct p_less {
  template<typename T, typename U>
  typename std::common_type<T, U>::type 
  operator()(T&& t, U&& u) const {
    return t < u ? t : u;
  }
};

// heterogeneous arguments possible
template<typename Head, typename... Args>
auto min(Head&& h, Args&&... args) -> typename std::common_type<Head, Args...>::type
{
  return fold(p_less(), std::forward<Head>(h), 
              std::forward<Args>(args)...);
}


// only considers homogeneous arguments
template<typename Head, typename... Args>
auto hmin(Head&& h, Args&&... args) -> Head
{
  return fold([](Head x, Head y) -> Head { return x < y ? x : y; }, 
              std::forward<Head>(h), std::forward<Args>(args)...);
}

int main()
{

  double x = 2.0, x2 = 3.0;
  int y = 2;

  auto d1 = min(3, 4.0, 2.f, 6UL);
  auto d2 = min(x, y);
  auto d3 = hmin(x, x2);
  auto b = hmin(3, 2, 7, 10);

  std::cout << d1 << std::endl;
  std::cout << d2 << std::endl;
  std::cout << d3 << std::endl;

  std::cout << b << std::endl;
  return 0;
}

然而,第一个版本要有趣得多。common_type查找所有参数都可以强制转换的类型。这是必要的,因为具有返回其中任何一个的异类参数的函数需要找到该类型。然而,这可以使用boost::variant来规避,但对于内置类型来说,这是没有意义的,因为无论如何都必须对它们进行强制比较。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/9762531

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档