2

This is similar to other questions I've seen, but given C++17's introduction of inline variables, it's worth asking. Consider this pattern:

auto to_ref = [](auto const& ptr) -> decltype(auto) { return *ptr; }

std::vector<std::unique_ptr<Foo>> foo_ptrs = from_somewhere();
for (Foo const& foo : foo_ptrs | transform(to_ref)) {
}

The to_ref generic lambda is...well, generic...so it makes sense to put it in a header so people aren't duplicating it everywhere.

My question: do the linkage considerations for templates also apply for generic lambdas? In other words, it is the responsibility of the compiler/linker to ensure that ODR is not violated for multiple instantiations of a given template with the same template arguments. Can I rely on that same behavior, or should I prepend the inline specifier to the auto to_ref = ...; specification?

Jarod42
  • 203,559
  • 14
  • 181
  • 302
KyleKnoepfel
  • 1,426
  • 8
  • 24
  • 1
    Not the same as this Q but a good read and a similar circumstance: https://stackoverflow.com/questions/34717823/can-using-a-lambda-in-header-files-violate-the-odr – NathanOliver Aug 27 '19 at 14:51

1 Answers1

5

to_ref is an object with a template operator(), it is not a template of any kind.

You will need to mark it inline to obey the ODR.

Caleth
  • 52,200
  • 2
  • 44
  • 75
  • 1
    Wouldn't types differ, as it is a lambda. – Jarod42 Aug 27 '19 at 14:56
  • @Jarod42 if it isn't `inline`, it doesn't matter either way. Otherwise [dcl.inline](https://timsong-cpp.github.io/cppwp/n4659/dcl.inline#6) implies there's only one object. "An inline function or variable shall be defined in every translation unit in which it is odr-used and shall have exactly the same definition in every case" – Caleth Aug 27 '19 at 15:09
  • But in this case your definition is `[](auto const& ptr) -> decltype(auto) { return *ptr; }` which should be a unique type every time it is evaluated, meaning the definition of the variable would be different in every TU. – NathanOliver Aug 27 '19 at 15:11
  • @NathanOliver it should be unique but why it should be different in every TU, what rule implies it? – ixSci Aug 27 '19 at 15:26
  • @NathanOliver that's OK by [basic.def.odr](https://timsong-cpp.github.io/cppwp/n4659/basic.def.odr) - "each definition of `D` shall consist of the same sequence of tokens". The requirements after that boil down to *those tokens mean the same thing* – Caleth Aug 27 '19 at 15:26
  • *"if it isn't `inline`, it doesn't matter either way"* Except if it is used in inline function... and your comment mostly contradict your answer as I understand it. – Jarod42 Aug 27 '19 at 15:26
  • @ixSci [This](https://timsong-cpp.github.io/cppwp/expr.prim.lambda#closure-1) says the type is unique. Since it has no qualification that means every lambda expression is a different type from every other lambda expression, even if lexically they are exactly the same. – NathanOliver Aug 27 '19 at 15:28
  • @Jarod42 if you have a namespace scope variable of some closure type, it's an ODR violation to have it in multiple translation units, so whether or not each occurence is the same type doesn't matter, you are already ill-formed. – Caleth Aug 27 '19 at 15:29
  • Yep. Missed that. Comment retracted – NathanOliver Aug 27 '19 at 15:34
  • @NathanOliver this line means only uniqueness. Nothing is said about different types. It is unclear how exactly will compilers handle that but generating, for the same line, multiple types doesn't look like a decent solution to me. In fact, I've just checked with MSVC and it is the same type across different TU. – ixSci Aug 27 '19 at 15:40
  • @ixSci: You might do thing like [that (`f(1)` and `f(2)` are actually different functions)](https://godbolt.org/z/x0P8do) with different lambda types. – Jarod42 Aug 27 '19 at 15:46
  • @Jarod42 `auto` in templates means it is not treated as a type but rather as a non-type parameter. Use your link and go to the "insights" website from there. There you can see only one lambda (closure) type. – ixSci Aug 27 '19 at 15:56
  • OK, misunderstood the first comment about missing `inline`. But still unsure that the lambda tokens means the same things (as `auto f = [](){}; auto g = [](){};`, `f` and `g` has different types). – Jarod42 Aug 27 '19 at 16:10
  • @ixSci: Code requires c++2a, and it seems [insights](https://cppinsights.io/s/c0f2c06d) has issue with that code (if there is only one instantiation, output should be different as currently 2 static is used) Changed to use type instead of `auto` for same result [Demo](https://godbolt.org/z/7uSm1c) but here [insights cannot compile it](https://cppinsights.io/s/8f67f62f). – Jarod42 Aug 27 '19 at 16:15
  • @Jarod42, lambda can't appear in non-evaluated context in C++17. Looks like it is changed in C++20 but clang didn't implement the change yet (MSVC neither). But this example work in gcc the way you describe so it seems that gcc does generate 2 different types (or the implementation does some weird buggy thing which appears as if it has 2 types). Anyway, I'm not arguing that it can't generate multiple types I'm just not sure that it is an actual requirement. – ixSci Aug 27 '19 at 16:38
  • That is my doubt about this answer. Does lambda type differ between TU or not? if so, `inline` is not enough to avoid ODR. – Jarod42 Aug 27 '19 at 16:43
  • @Jarod42 for it to be an ODR-violation we should find a rule which states it. Having unique type doesn't imply generating multiple types and thus we can't infer ODR-violation out of it. Look at [this document](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0315r3.pdf) it also has examples where the same lambda (tokens) [even] on different lines produces single type. – ixSci Aug 27 '19 at 17:00
  • @ixSci: As I understand from 2.6 which is similar to OP's case, it is UB. but they didn't use `inline` neither :/ – Jarod42 Aug 27 '19 at 17:33