Lambda变量在翻译单元之间共享。

huangapple go评论62阅读模式
英文:

Lambda variable shared between translation units

问题

I have a variable that I share in a header file. It is initialized with a lambda. I wish to call a function from another translation unit, taking this lambda variable as a parameter.

With gcc, I get an error:

> used but never defined [-fpermissive].

Here is a cut down example:

// in shared header file
//
const auto make_lambda() { return [](){ }; }
inline const auto lambda = make_lambda();

// in cpp file
//
void func(const decltype(lambda)&);

int main() {
    func(lambda);
}

My understanding is that the lambda type should be shared between translation units.

When I alter the lambda variable to the following, the issue goes away:

inline const auto lambda = [](){ }; // <-- this does work
英文:

I have a variable that I share in a header file. It is initialized with a lambda. I wish to call a function from another translation unit, taking this lambda variable as a parameter.

With gcc, I get an error:

> used but never defined [-fpermissive].

Here is a cut down example:

// in shared header file
//
const auto make_lambda() { return [](){ }; }
inline const auto lambda = make_lambda();

// in cpp file
//
void func(const decltype(lambda)&);

int main() {
    func(lambda);
}

My understanding is that the lambda type should be shared between translation units.

When I alter the lambda variable to the following, the issue goes away:

inline const auto lambda = [](){ }; // <-- this does work

答案1

得分: 1

Clang 提供了一份出色的诊断,很好地解释了 make_lambda 中的问题:

<source>:8:6: 错误:函数 'func' 在此翻译单元中被使用但未定义,
                     并且由于其类型没有链接,因此不能在任何其他翻译单元中定义
void func(const decltype(lambda)&amp;);
     ^

拥有 "没有链接" 意味着:

当名称没有链接时,它所表示的实体不能被其他作用域中的名称引用。

- [basic.link]/2.4

我们的 Lambda 没有链接的原因是,它定义的闭包类型是在 make_lambda 内部定义的。这导致了:

不受这些规则覆盖的名称没有链接。
此外,除非另有说明,块作用域声明的名称没有链接。

- [basic.link]/7

您试图使用的类型实际上是 make_lambda::__lambda,这不能跨不同的翻译单元使用。

共享 Lambda 表达式的 ODR 问题

inline const auto lambda = [](){ };

...会消除编译器错误,但这将使您的程序非法,无需诊断。我们必须遵守一次定义规则,并违反了以下规定:

在每个这种定义中,除了在 'D' 的默认参数和默认模板参数中之外,相应的 Lambda 表达式应具有相同的闭包类型(见下文)。

- [basic.def.odr]/14.6

每个翻译单元将有其自己独特的闭包类型,但 lambda 必须在每个地方具有相同的定义。这是一次 ODR 违规。

如果您坚持要这样做,安全的方法是将其放入一个类类型中:

struct dummy {
    using lambda_type = [] {};
};

dummy::lambda_type 将具有链接,并且不违反 ODR。
另请参阅:[basic.def.odr]/16

结论

不要在不同的翻译单元之间共享 Lambda。即使您使其工作,解决方案也不够美观。相反,考虑以下选项:

  • 编写一个接受任何可调用对象的函数模板
  • 接受函数指针
  • 接受 std::function
  • 创建一个常规类,而不是 Lambda,该类具有重载的调用运算符

所有这些选项都比尝试在不同的翻译单元之间共享 Lambda 表达式(在函数之外)要好得多。

英文:

Clang provides an excellent diagnostic that explains the problem with make_lambda well:

&lt;source&gt;:8:6: error: function &#39;func&#39; is used but not defined in this translation unit,
                     and cannot be defined in any other translation unit because its type
                     does not have linkage
void func(const decltype(lambda)&amp;);
     ^

Having "no linkage" means:
> When a name has no linkage, the entity it denotes cannot be referred to by names from other scopes.

- [basic.link]/2.4

The reason why our lambda has no linkage is that the closure type it defines is defined inside of make_lambda. This results in:

> Names not covered by these rules have no linkage.
Moreover, except as noted, a name declared at block scope has no linkage.

- [basic.link]/7

The type you're trying to use is make_lambda::__lambda essentially, and this cannot be used across different TUs.

ODR issues with sharing lambda expressions

inline const auto lambda = [](){ };

... makes the compiler errors go away, but this makes your program ill-formed, no diagnostic required. We must respect the one-definition-rule, and we violate the following:

> In each such definition, except within the default arguments and default template arguments of D, corresponding lambda-expressions shall have the same closure type (see below).

- [basic.def.odr]/14.6

Every TU will have its own unique closure type, but lambda must have the same definition everywhere. This is an ODR violation.

If you really insisted on doing it, the safe way would be to put it into a class type:

struct dummy {
    using lambda_type = [] {};
};

dummy::lambda_type will have linkage and doesn't violate the ODR.
See also: [basic.def.odr]/16

Conclusion

Just don't share lambdas across different TUs. Even if you make it work, the solution isn't pretty. Instead, consider:

  • writing a function template that accepts anything callable
  • accepting a function pointer
  • accepting std::function
  • creating a regular class, not a lambda, which has an overloaded call operator

All of these options are much better than trying to share a lambda expression (outside of functions) across TUs.

huangapple
  • 本文由 发表于 2023年6月30日 00:26:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/76582942.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定