【问题标题】:What can be said about type of lambda closure defined in function template?关于函数模板中定义的 lambda 闭包类型可以说什么?
【发布时间】:2020-01-08 15:34:17
【问题描述】:

我知道 lambda 的类型是“隐藏的”并且是唯一的。但是“唯一”的确切含义是什么,相同的 lambda 代码是否给出相同的类型?

(如果可能)我想使用这个“不变量”进行类型检查,如下所示:

#include <type_traits>

template <typename T>
auto foo(const T y)
{
  return [=](T x) { return x * y; }; 
}

int main()
{
  auto f1 = foo<int>(4.);
  auto f2 = foo<int>(6.); // fails with auto f2 = foo<double>(6.);

  static_assert(std::is_same_v<decltype(f1), 
                               decltype(f2)>); // is this ok or undefined behavior?
}

在此之前,我想确定 c++ 标准是怎么说的:它是正常的还是未定义的行为?

(这段代码被g++和clang++编译成功,但只是标准真题)


作为补充更符合最初的标题(抱歉,这有点晚了,因为我不得不离开我的办公室),这是一个更强大的版本,编译:

#include <type_traits>

template <typename T>
auto foo_1(const T y)
{
  return [=](T x) { return x * y; };
}

template <typename T>
auto foo_2(const T y)
{
  return [=](T x) { return x * y; };
}

int main()
{
  auto f1 = foo_1<int>(4.);
  auto f2 = foo_2<int>(6.);

  static_assert(std::is_same_v<decltype(f1), decltype(f2)>); // fails!
}

【问题讨论】:

  • 嗯,看起来foo&lt;int&gt; 是一个函数,一个函数具有特定的返回类型。所以它返回的每个对象都需要具有相同的类型。我怀疑由于这个原因f1f2 必然具有相同的类型。
  • @FrançoisAndrieux 这句话很有趣,谢谢。
  • 对于它的价值,here 是标准对闭包类型的规定。
  • 恕我直言,标题与问题正文不匹配。您没有相同的 lambda 表达式,您有 一个 lambda(表达式 [...](...){...} 出现一次)(更准确地说,每个模板实例化都有一个,但这里只有一个实例化)。跨度>
  • 仅根据问题标题,this example 可能会回答该问题。我尝试过的编译器并不认为相同的 lambda 具有相同的类型。

标签: c++ lambda language-lawyer


【解决方案1】:

确实,标准并没有明确说明“独特”的含义,但我认为我们可以弄清楚。

首先,请注意标准中的“唯一、未命名的非联合类类型”。形容词“未命名”足以暗示 lambda 闭包类型永远不会与使用 structclass 关键字或标准库定义的某些命名类相同(例如,lambda 闭包类型不能是 @ 987654324@).

那么,为什么标准会说“独特的、未命名的”?它一定告诉我们一些关于两种不同 lambda 闭包类型之间的比较。不过,在许多情况下,这将是多余的。如果我们有两个 lambda 表达式,例如:

auto f1 = [](int x) { return x; };
auto f2 = [](int x) { return 2*x; };

很明显,两个 lambda 表达式不能具有相同的类型,因为它们的函数调用运算符具有不同的行为。因此,每个闭包类型是“唯一”的声明不会告诉我们任何关于f1f2 的未知信息。

但是,如果我们现在看:

auto f3 = [](int x) { return x; };
auto f4 = [](int x) { return x; };

在这里,“唯一”可以解释为 f3f4 肯定有不同的类型,即使 lambda 表达式具有相同的拼写和相同的行为。

在这种情况下,这通常被认为是“独特”的含义。如果标准不是说f3f4 有不同的类型,那么根本没有理由使用“唯一”这个词。相反,它只会说“未命名”,而不是“唯一的、未命名的”。

接下来的问题是,唯一性延伸到什么程度。这可能意味着 lambda 表达式的每次评估都会产生不同的类型吗?不,不可能是这个意思。这将使表达式的静态类型取决于它已执行的次数(运行时属性),这是荒谬的。考虑一下如果每次执行 foo&lt;int&gt; 时您的 foo&lt;int&gt; 的 lambda 具有不同的类型会发生什么。这意味着foo&lt;int&gt; 的签名在每次调用时都会改变。这不可能。 foo&lt;int&gt; 中的 lambda 必须始终具有相同的类型。

至于您的foo_1foo_2 的情况,这两种lambda 类型肯定是不同的。同样,如果不是这样,“独特”一词将是多余的。

【讨论】:

  • 出色的演绎,亲爱的夏洛克,虽然,我可能允许我自己为这个完整的程序给我所有的答案,我需要在同一个主题上。 -- wandbox.org/permlink/V3TJb20cKjgY2Bi2
  • “不,不可能是那个意思。” ——我认为我们也可以围绕 ODR 展开争论?但我想结论只会是“如果这是真的,你永远不能使用 lambdas”,这在理论上并不排除。 ;)
【解决方案2】:

您所做的断言不是未定义的行为。闭包类型是 impl 定义的。一旦你在同一个编译器中比较了 lambda 表达式类型,就没有问题了。

【讨论】:

  • 是这样吗?我认为只有标准要求实现定义的东西才能被认为是实现定义。我在闭包类型部分(7.5.5.1) 中没有看到这一点,所以我不确定该术语是否适用。除非我错过了它或误解了实现定义行为的性质。编辑:此外,实现定义的行为需要由实现记录。我不知道我是否知道任何记录他们使用的闭包类型的实现。
  • @FrançoisAndrieux 它被称为具有已定义属性的“未命名”类型。它不是按照标准定义的实现。
  • 我同意这不是 UB;但你还没有证明它是 impl-defined。
  • @Yakk-AdamNevraumont,我可能会如此大胆地假设,如果在每个编译器中重复此操作,输出将完全不同,以至于可以声称“impl-definedness”——wandbox.org/permlink/V3TJb20cKjgY2Bi2跨度>
  • 当然,必须添加这一点,才能正面迎接,怪异和惊奇,充满和荒谬,当今 C++ 编译器之间的差异......
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-09-19
  • 1970-01-01
  • 1970-01-01
  • 2019-09-06
  • 2020-07-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多