跟我学c++中级篇——列表初始化和折叠表达式的灵活运用

一、从小例子开始

最近在开发的过程中发现有一些小例子可以灵活的应用列表初始化和折叠表达式,总结出来看一下:
1、列表初始化细节,看下面的常见的例子:

template <size_t... N> static constexpr auto sqs(size_t index, std::index_sequence<N...>) {
  constexpr auto nums = std::array{N * N...};
  return nums[index];
}

template <size_t N> constexpr static auto conSqs(size_t id) {
  return sqs(id, std::make_index_sequence<N>{});
}

void test() { static_assert(conSqs<30>(10) == 10 * 10); }

这里的问题在于,std::index_sequence会由std::make_index_sequence{}生成一个空的对象,它的目的在于拿到N…的序列,也就是说,对象的内容不重要,重要的是模板参数的获取。比如<0,1,2,3,4,5,6,7,8,9>就可以在std::array{N * N…},通过列表初始化来实现平方的数组,即std::array{0×0,1×1,…9*9}。要注意std::initializer_list的形式,这就是其中的一种。
其实通过变参实现可以更好理解这个问题:

#include 
template <typename... T> void testarray(T... t) {
  auto a = std::array{t * t...}; //列表初始化
}

2、逗号表达式和列表初始化以及折叠表达式的共同工作
在前面的模板学习中,曾经给过一个例子:

template < typename T>
int Add(T arg)
{
    return    arg;
}

template < typename... Args>
int AddValue(Args ... args)
{
    //也可使用类似int v[] = { (Add(args),0)... };这种逗号表达式代码,只是强制存储0到V数组中
    int v[] = { Add(args)... };
    int d = 0;

    for (auto n : v)
    {
        d += n;
    }

    return    d;
}

看上面的注释可能对小白还是不太友好,对比一下下面的代码就明白了:

#include 
template <typename... T> void testarray(T... t) {
  auto sum = (..., (t * t));
}

3、折叠表达式的灵活运用
看一下代码:

template <typename... Args> void displayPars(Args &&...args) 
{ 
   (std::cout << ... << args) << std::endl; 
}
template <typename F, typename... Args> void forPars(F &&f, Args &&...args) 
{ 
  (f(args), ...); 
}

void foreachPars() {
  displayPars(11, 3.3, "tom");                                                  
  forPars([](auto par) { std::cout << par << std::endl; }, 6, 'a', "5");            
}

可以用它来打印或者操作变参模板的变参,这和前面在讲切片编程时的make_index_sequence的展开方式有相似之处。

二、扩展

其实类似的小例子有很多,但有的时候儿是觉得太简单不愿意记下来,有的时候可能真是的没有想到,但在实际应用中,往往有人问个为什么会发现其实对一些细节其实并没有完全分析清楚,这就需要在后面不断的进行总结。比如遇到一个问题,类似代码:

#include 
void testTuple() {
  std::tuple<int> a{10};
  std::tuple<std::string> b{"abc"};
  std::tuple<double> c{0.7};

  auto dd = std::tuple_cat(a, b, c);
  std::cout << "cur value:" << std::get<0>(dd) << std::get<1>(dd) << std::get<2>(dd) << std::endl;
}

意思是说,工程中需要一个tuple或者类似的容器来放置不同类型的对象(不能使用基类继承的方式)。那么如何解决动态扩展的问题?想来想去,想到了tuple的底层实现,通过std::get这种方式来处理。但这又有一个问题,如何遍历它?这个在文章的开始已经提到了,包括在切片编程中使用的方式,使用make_index_sequence来展开ID,并不断的判断从而得到相关的数据类型:

#include 
#include 

template <typename Tp, std::size_t... ID> void DisplayTuple(const Tp &tp, std::index_sequence<ID...>) {
  ((std::cout << std::get<ID>(tp) << std::endl), ...);
}

template <typename... Args> void GetTuple(const std::tuple<Args...> &tp) {
  DisplayTuple(tp, std::index_sequence_for<Args...>{});//等同std::make_index_sequence{}
}

int main() {
  std::tuple<int, double, char, std::string> tp{66, 8.0, 'a', "tom"};
  GetTuple(tp);

  return 0;
}

所以说灵活的掌握技术不是简单的一句灵活就可以真正明白的,这需要不断的实践来操作。
再看一个例子:

int TestFunc(int d) {
  std::cout << "this is test!" << std::endl;
  return d + 100;
}
//first
template <typename R, typename F, typename... Args,
          class = typename std::enable_if<!std::is_member_function_pointer<F>::value>::type>
void funcPack(F &&f, Args &&...args) {
  std::function<R()> m_func = [&] { return f(args...); };
}

//second
template <typename F, typename... Args> void GetClassFromName(F &&f, Args... args) { f(std::forward<Args>(args)...); }
int main() {

  funcPack<int>(TestFunc, 200);
  GetClassFromName(TestFunc, 100);
  
  return 0;
}

第二个例子就很好理解,一般来说跑一下就明白了。但第一个例子呢?这个其实就是前面提到的模板的匹配的问题即模板的等价,其实仍然是一个基础问题。在写模板代码时,如果代码有问题,可以先把一些限定条件去除(如std::enable_if之类),等基本流程通过后再加控制选择,这也是一个快速定位BUG的方法。

三、总结

边学习边总结,这其实就是理论和实践结合的一种方式,各人有各人的方法,只要目标是一致的,就可以了。C++的细节还有非常多,这就需要不断的在实际的工程中遇到后真正的深入了解其中的运行机制,然后 才可能灵活的运用。

你可能感兴趣的:(C++11,C++,c++)