跟我学C++中级篇——common_type的应用

一、common_type

在C++11标准中引入了common_type,在C++14中引入了common_type_t,其实只要看名字,大概也能猜出这个这个元编程库类的作用。它主要是用来获取参数的共有类型的,需要注意的,它获取的类型是退化的,因为它内部调用了std::decay。先看一个原来的例程:

template <class... Args> auto Rorder(Args &&...args) {
  std::vector<std::common_type_t<Args...>> vec{};
  bool bOk = false;
  (bOk = ... = (vec.push_back(args), false));

  return vec;
}
int main() {
  std::vector<int> vec = Rorder(1, 2, 3, 4, 5, 6, 'a');
  for (auto &v : vec) {
    std::cout << v << std::endl;
  }
}

这个例程的作用是将输入的排序逆序。它里面的 common_type_t其实就是保证vec存储的数据的一致性。在标准库的定义中,如果std::common_type_t中的参数列表都可以转化成一个共同类型,则类型存在,否则不存在。
它特别适合于在算术运算的场景下应用(比如重载运算符),能确保数据类型的一致性。

二、定义和说明

common_type的定义如下:

template< class... T >
struct common_type;

它的使用比较有意思:
1、如果无模板参数,则无类型。即sizeof…(T) 为零,则无成员 type
2、如果一个模板参数,则等同于这个类型(std::common_type::type),否则也是无type
3、如果有两个模板参数,要找一个可退化到相同的类型,否则无type;如果有自定义物化,则使用该特化;另外对于一些the conditional operator的操作按照相关的标准定义来操作。
同时C++20中的新定义参看相关的新标准
4、如果大于两个模板参数,则等同于两个模板参数,否则无type
注意,其参数类型是完整类型,但使用后为退化的类型,即可以从cv限定到无cv限定。

看一个库里的例子:

#include 
#include 
#include 

// std::chrono already finds the greatest common divisor,
// likely using std::common_type<>. We make the type
// deduction externally.

template <typename T, typename S>
constexpr auto durationDiff(const T &t, const S &s) -> typename std::common_type<T, S>::type {
  typedef typename std::common_type<T, S>::type Common;
  return Common(t) - Common(s);
}

int main() {
  using namespace std::literals;

  constexpr auto ms = 30ms;
  constexpr auto us = 1100us;
  constexpr auto diff = durationDiff(ms, us);

  std::cout << ms.count() << " - " << us.count() << " = " << diff.count() << '\n';
  
  return 0;
}

这个例子很明白,运行后退化到了微秒。这个就好了,计算时间时可以省转换的代码过程。然后再看一个返回值的例子:

template <typename T1, typename T2> std::common_type_t<T1, T2> CurMin(T1 t1, T2 t2) {
  return t2 > t1 ? t1 : t2;
}

int main() {

  auto r1 = CurMin(11.7, 3.5);
  auto r2 = CurMin(7.1, 9);

  std::cout << "CurMin value:" << r1 << std::endl;
  std::cout << "CurMin value:" << r2 << std::endl;
  return 0;
}

三、扩展

那么在处理常见的继承上呢?看下面的程序:

struct B {
  B() { std::cout << "cur is B" << std::endl; }
};
struct D : B {
  D() { std::cout << "cur is B" << std::endl; }
};
void testClassD() {
  typedef std::common_type<D, B>::type T1;
  typedef std::common_type<D *, B *>::type T2;

  T1 t1;
  std::cout << std::is_same_v<T2, B *> << std::endl;
  std::cout << std::is_same_v<T2, D *> << std::endl;
}

运行结果发现都是基类型才是共同的类型,这也符合前面的说明的中的情况。
同样,在上面的两个参数中说明可以自己实现特化,那么就照葫芦画瓢,来一个例子:


struct Ex0 {
  int a = 0;
};
struct Ex1 {
  int a = 1;
};

namespace std {
template <> struct common_type<Ex0, Ex1> { using type = Ex0; };
} 
void testOwner() {
  using type = std::common_type_t<Ex0, Ex1>;
  type m;
  std::cout << "cur class value:" << m.a << std::endl;
}

通过运行的值可以看到确实是指定的Ex0,这就说明自定义的特化被实现了。

四、总结

这些元编程中用的相关库类型,在某些场景下是非常有用并且好用的。不过还是那句话,还是要多用,多反复的印证。用得多了,自然有些不好理解的东西就会豁然开朗,不言而喻。

你可能感兴趣的:(C++11,C++,模板,c++,开发语言)