为什么我不能 return 来自 lambda 的初始化列表
Why I can not return initializer list from lambda
为什么此代码无效?
auto foo = [] () {
return {1, 2};
};
但是,这是有效的,因为 initializer_list
仅用于初始化 vector
而不是 return 本身:
auto foo = [] () -> std::vector<int> {
return {1, 2};
};
为什么我不能returninitializer_list
?它可能有用。例如,可用于初始化 vector
或 list
或...的 lambda 具有某些默认值。
std::initializer_list
无法通过模板参数推导出来,这意味着您必须明确告诉 lambda 它是什么:
#include <initializer_list>
#include <iostream>
#include <vector>
int main()
{
auto foo = []() -> std::initializer_list<int> { return {1, 2}; };
std::vector<int> bar{foo()};
for (int x : bar) { std::cout << x << " "; };
}
Demo. Here's the rationale behind this from the initializer list proposal:
Can an initializer list be used as a template argument? Consider:
template<class T> void f(const T&);
f({ }); // error
f({1});
f({1,2,3,4,5,6});
f({1,2.0}); // error
f(X{1,2.0}); // ok: T is X
There is obviously no problem with the last call (provided X{1,2.0} itself is valid)
because the template argument is an X. Since we are not introducing arbitrary lists of
types (product types), we cannot deduce T to be {int,double} for f({1,2.0}), so that call is
an error. Plain {} does not have a type, so f({}) is also an error.
This leaves the homogeneous lists. Should f({1}) and f({1,2,3,4,5,6}) be accepted? If so,
with what meaning? If so, the answer must be that the deduced type, T, is
initializer_list. Unless someone comes up with at least one good use of this simple
feature (a homogeneous list of elements of type E is deduced to be an
initializer_list), we won’t propose it and all the examples will be errors: No template
argument can be deduced from an (unqualified) initializer list. One reason to be cautious
here is that we can imagine someone getting confused about the possible interpretations
of single-element lists. For example, could f({1}) invoke f<int>(1)? No, that would be
quite inconsistent.
您可以从函数中 return 一个 initializer_list
:
return std::initializer_list<int>{1, 2};
或
auto ret = {1, 2};
return ret;
原因是,auto
变量声明使用的规则不同于 auto
return 类型推导。第一个对这种情况有特殊规则,第二个使用普通模板类型推导。
这在 Scott Meyers Effective Modern C++,第 2 项中进行了详细讨论。他还就此主题发表了 video and slides。
Lambda return 类型推导使用 auto
规则,通常可以很好地推导 std::initializer_list
。但是,语言设计者禁止从 return 语句 ([dcl.spec.auto]/7):
中的花括号初始化列表中进行推导
If the deduction is for a return
statement and the initializer is a
braced-init-list ([dcl.init.list]), the program is ill-formed.
原因是 std::initializer_list
具有引用语义 ([dcl.init.list]/6)。
[]() -> std::initializer_list<int> { return {1, 2}; }
和
[]() -> const int & { return 1; }
一样糟糕。 initializer_list
对象的后备数组的生命周期在 lambda returns 时结束,你会留下一个(或两个)悬挂指针。
Demo:
#include <vector>
struct Noisy {
Noisy() { __builtin_printf("%s\n", __PRETTY_FUNCTION__); }
Noisy(const Noisy&) { __builtin_printf("%s\n", __PRETTY_FUNCTION__); }
~Noisy() { __builtin_printf("%s\n", __PRETTY_FUNCTION__); }
};
int main()
{
auto foo = []() -> std::initializer_list<Noisy> { return {Noisy{}, Noisy{}}; };
std::vector<Noisy> bar{foo()};
}
输出:
Noisy::Noisy()
Noisy::Noisy()
Noisy::~Noisy()
Noisy::~Noisy()
Noisy::Noisy(const Noisy&)
Noisy::Noisy(const Noisy&)
Noisy::~Noisy()
Noisy::~Noisy()
请注意在到目前为止创建的所有 Noisy
对象都已被销毁后如何调用复制构造函数。
为什么此代码无效?
auto foo = [] () {
return {1, 2};
};
但是,这是有效的,因为 initializer_list
仅用于初始化 vector
而不是 return 本身:
auto foo = [] () -> std::vector<int> {
return {1, 2};
};
为什么我不能returninitializer_list
?它可能有用。例如,可用于初始化 vector
或 list
或...的 lambda 具有某些默认值。
std::initializer_list
无法通过模板参数推导出来,这意味着您必须明确告诉 lambda 它是什么:
#include <initializer_list>
#include <iostream>
#include <vector>
int main()
{
auto foo = []() -> std::initializer_list<int> { return {1, 2}; };
std::vector<int> bar{foo()};
for (int x : bar) { std::cout << x << " "; };
}
Demo. Here's the rationale behind this from the initializer list proposal:
Can an initializer list be used as a template argument? Consider:
template<class T> void f(const T&); f({ }); // error f({1}); f({1,2,3,4,5,6}); f({1,2.0}); // error f(X{1,2.0}); // ok: T is X
There is obviously no problem with the last call (provided X{1,2.0} itself is valid)
because the template argument is an X. Since we are not introducing arbitrary lists of
types (product types), we cannot deduce T to be {int,double} for f({1,2.0}), so that call is
an error. Plain {} does not have a type, so f({}) is also an error.This leaves the homogeneous lists. Should f({1}) and f({1,2,3,4,5,6}) be accepted? If so,
with what meaning? If so, the answer must be that the deduced type, T, is
initializer_list. Unless someone comes up with at least one good use of this simple
feature (a homogeneous list of elements of type E is deduced to be an
initializer_list), we won’t propose it and all the examples will be errors: No template
argument can be deduced from an (unqualified) initializer list. One reason to be cautious
here is that we can imagine someone getting confused about the possible interpretations
of single-element lists. For example, could f({1}) invoke f<int>(1)? No, that would be
quite inconsistent.
您可以从函数中 return 一个 initializer_list
:
return std::initializer_list<int>{1, 2};
或
auto ret = {1, 2};
return ret;
原因是,auto
变量声明使用的规则不同于 auto
return 类型推导。第一个对这种情况有特殊规则,第二个使用普通模板类型推导。
这在 Scott Meyers Effective Modern C++,第 2 项中进行了详细讨论。他还就此主题发表了 video and slides。
Lambda return 类型推导使用 auto
规则,通常可以很好地推导 std::initializer_list
。但是,语言设计者禁止从 return 语句 ([dcl.spec.auto]/7):
If the deduction is for a
return
statement and the initializer is a braced-init-list ([dcl.init.list]), the program is ill-formed.
原因是 std::initializer_list
具有引用语义 ([dcl.init.list]/6)。
[]() -> std::initializer_list<int> { return {1, 2}; }
和 []() -> const int & { return 1; }
一样糟糕。 initializer_list
对象的后备数组的生命周期在 lambda returns 时结束,你会留下一个(或两个)悬挂指针。
Demo:
#include <vector>
struct Noisy {
Noisy() { __builtin_printf("%s\n", __PRETTY_FUNCTION__); }
Noisy(const Noisy&) { __builtin_printf("%s\n", __PRETTY_FUNCTION__); }
~Noisy() { __builtin_printf("%s\n", __PRETTY_FUNCTION__); }
};
int main()
{
auto foo = []() -> std::initializer_list<Noisy> { return {Noisy{}, Noisy{}}; };
std::vector<Noisy> bar{foo()};
}
输出:
Noisy::Noisy()
Noisy::Noisy()
Noisy::~Noisy()
Noisy::~Noisy()
Noisy::Noisy(const Noisy&)
Noisy::Noisy(const Noisy&)
Noisy::~Noisy()
Noisy::~Noisy()
请注意在到目前为止创建的所有 Noisy
对象都已被销毁后如何调用复制构造函数。