Black Magic使用Initializer_list和包扩展 [英] Black Magic using Initializer_list and pack expansion

查看:47
本文介绍了Black Magic使用Initializer_list和包扩展的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

要扩展灵活的函数参数,有一种使用 std :: initializer_list 的方法.但是我听不懂.谁能以一种可以理解的方式解释这一点?

To expand flexible function parameters, there is a method using std::initializer_list. However I couldn't understand it. Can anyone explain this in an understandable way?

template<typename T, typename... Args>
auto print(T value, Args... args) {
    std::cout << value << std::endl;
    return std::initializer_list<T>{([&] {
        std::cout << args << std::endl;
    }(), value)...};
}

推荐答案

这是一种非常混乱的处理方式,但是C ++ 14要求我们做类似的事情.我将解释其局限性以及为什么这样做(尽管有更清晰的方法可以做到这一点).

This is a very confused way of doing things, but C++14 requires that we do something similar. I'll explain the limitations and why it gets done this way (though there are clearer ways to do it).

此代码的目标是重复打印在单独一行中给出的每个参数.由于该函数是可变参数模板,因此需要在表达式 std :: cout<<上使用pack扩展.args<<std :: endl .

The goal of this code is to repeatedly print out each parameter it is given on a separate line. Since the function is a variadic template, it needs to use a pack expansion on the expression std::cout << args << std::endl.

您的第一个想法可能是(std :: cout<< args<< std :: endl)...; .但是,这实际上不是您可以在C ++ 14中执行的有效操作.实际上,您只能在C ++ 14中以逗号分隔的值序列(例如,函数的参数列表或其他内容)的上下文中执行包扩展.您不能只是将包装扩展为裸露的语句.

Your first thought might be (std::cout << args << std::endl) ...;. However, that's not actually a valid thing you can do in C++14. In fact, you can only perform a pack expansion in the context of a comma-delimited sequence of values in C++14, such as a list of arguments to a function or whatever. You can't just expand a pack as a naked statement.

好吧, 可以将包扩展到其中的一个地方是braced-init-list(用于初始化对象的 {} ).但是, {(std :: cout<< args<< std :: endl)...}; 也不起作用.扩展没有任何问题.问题是braced-init-list本身.在语法上,只有在初始化对象时,才会出现braced-init-list.裸露的 {} 作为语句不会初始化任何内容.所以你不能在那里使用它.

Well, one place that you can expand a pack into is a braced-init-list (the {} for initializing objects). However, {(std::cout << args << std::endl) ...}; doesn't work either. There's nothing wrong with the expansion; the problem is the braced-init-list itself. Grammatically, a braced-init-list can only appear when you are initializing an object. And a naked {} as a statement doesn't initialize anything. So you can't use it there.

因此,您必须使用 {} 进行初始化.典型的用法是初始化一个空数组.例如:

So you have to use the {} to initialize something. The typical idiom for this is to initialize an empty array. For example:

int unused[] = {0, ((std::cout << args << std::endl), 0)...};

args 为空的情况下,需要初始的 0 .您不能初始化没有元素的未调整大小的数组.扩展表达式中的尾随,0 是逗号表达式的一部分.

The initial 0, is needed in case args is empty; you can't initialize an unsized array with no elements. The trailing , 0 in the expansion expression is part of a comma expression.

在C ++中,表达式(1,2)表示评估表达式1,然后舍弃其值,评估表达式2,并将其用作总表达式的结果.";因此在pack扩展中使用它意味着`输出一个参数,丢弃结果,并使用0作为表达式的结果.因此,就表达式的结果而言,每次扩展都只是说"0"的一种真正奇特的方式.

In C++, the expression (1, 2) means "evaluate the expression 1, then discard its value, evaluate the expression 2, and use that as the result of the total expression." So using that in the pack expansion means `output one argument, discard the result, and use 0 as the result of the expression. So each pack expansion is just a really fancy way of saying "0", as far as the result of the expression is concerned.

最后,未使用仅存储一堆零.我们使用 unused 初始化的副作用,作为强制C ++解压缩包扩展的一种方式.

In the end, unused just stores a bunch of zeros. We use a side-effect of the initialization of unused as a way to force C++ to unpack a pack expansion.

在您显示的代码中,用户决定使用braced-init-list直接初始化 initializer_list< T> .这也是有效的,并且具有处理空的 args 的次要好处.问题在于用户然后返回了该对象.这很糟糕,因为没有人可以实际使用该返回值.

In the code you show, the user decided to use the braced-init-list to directly initialize an initializer_list<T>. That's valid as well, and it has the minor benefit of working on an empty args. The problem is that the user then returns this object. This is bad because nobody can actually use that return value.

initializer_list 不拥有它们引用的对象.在braced-init-list的引用处创建了一个临时数组,该列表保存了这些对象; initializer_list 只是指向该数组.临时数组将在 return 语句的末尾销毁,因此调用方将获得一个 initializer_list ,该指针指向一堆 destroyed 对象.另外,由于没有人可以使用返回值,因此它会不必要地调用 T 的副本构造函数.

initializer_lists don't own the objects they reference. A temporary array is created at the cite of the braced-init-list which holds those objects; the initializer_list just points into that array. The temporary array will be destroyed at the end of the return statement, so the caller will get an initializer_list that points into a bunch of destroyed objects. Also, it invokes the copy constructor of T a bunch of times needlessly, since nobody can use the return value.

因此,这是一个常见成语的令人困惑和糟糕的例子.最好删除 return 并将其设置为 void 函数.

So this is a confused and bad example of a common idiom. It would be better to remove the return and just make it a void function.

C ++ 17允许我们直接执行此操作而不必初始化数组:

C++17 just lets us do this directly without having to initialize an array:

((std::cout << args << std::endl), ...);

这是折叠表达式.它将首先在 args 中的值上调用每个子表达式.

This is a fold expression over the comma operator. It will invoke each sub-expression first to last on the values in args.

这篇关于Black Magic使用Initializer_list和包扩展的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆