70

Can the following be written in a header file:

inline void f () { std::function<void ()> func = [] {}; }

or

class C { std::function<void ()> func = [] {}; C () {} };

I guess in each source file, the lambda's type may be different and therefore the contained type in std::function (target_type's results will differ).

Is this an ODR (One Definition Rule) violation, despite looking like a common pattern and a reasonable thing to do? Does the second sample violate the ODR every time or only if at least one constructor is in a header file?

Columbo
  • 60,038
  • 8
  • 155
  • 203
Alex Telishev
  • 2,264
  • 13
  • 15
  • Maybe relevant: https://stackoverflow.com/questions/6025118/specifying-a-lambda-function-as-default-argument – Jonathan Potter Jan 11 '16 at 09:31
  • Each time you construct an other lambda (unrelated to the previous). – Jarod42 Jan 11 '16 at 09:31
  • Are you asking if the declarations are ODR violations if they exist in more than one header file, if using it in multiple cpp files is an ODR violation, or if using it in *further* inline functions in header files would be an ODR violation, or some/none/all of the above? – Yakk - Adam Nevraumont Jan 11 '16 at 14:48
  • 1
    I've adjusted the question, since otherwise an explanation of default arguments is needed (complicating the explanation). – Columbo Jan 13 '16 at 15:54

2 Answers2

43

This boils down to whether or not a lambda's type differs across translation units. If it does, it may affect template argument deduction and potentially cause different functions to be called - in what are meant to be consistent definitions. That would violate the ODR (see below).

However, that isn't intended. In fact, this problem has already been touched on a while ago by core issue 765, which specifically names inline functions with external linkage - such as f:

7.1.2 [dcl.fct.spec] paragraph 4 specifies that local static variables and string literals appearing in the body of an inline function with external linkage must be the same entities in every translation unit in the program. Nothing is said, however, about whether local types are likewise required to be the same.

Although a conforming program could always have determined this by use of typeid, recent changes to C++ (allowing local types as template type arguments, lambda expression closure classes) make this question more pressing.

Notes from the July, 2009 meeting:

The types are intended to be the same.

Now, the resolution incorporated the following wording into [dcl.fct.spec]/4:

A type defined within the body of an extern inline function is the same type in every translation unit.

(NB: MSVC isn't regarding the above wording yet, although it might in the next release).

Lambdas inside such functions' bodies are therefore safe, since the closure type's definition is indeed at block scope ([expr.prim.lambda]/3).
Hence multiple definitions of f were ever well-defined.

This resolution certainly doesn't cover all scenarios, as there are many more kinds of entities with external linkage that can make use of lambdas, function templates in particular - this should be covered by another core issue.
In the meantime, Itanium already contains appropriate rules to ensure that such lambdas' types coincide in more situations, hence Clang and GCC should already mostly behave as intended.


Standardese on why differing closure types are an ODR violation follows. Consider bullet points (6.2) and (6.4) in [basic.def.odr]/6:

There can be more than one definition of […]. Given such an entity named D defined in more than one translation unit, then each definition of D shall consist of the same sequence of tokens; and

(6.2) - in each definition of D, corresponding names, looked up according to [basic.lookup], shall refer to an entity defined within the definition of D, or shall refer to the same entity, after overload resolution ([over.match]) and after matching of partial template specialization ([temp.over]), […]; and

(6.4) - in each definition of D, the overloaded operators referred to, the implicit calls to conversion functions, constructors, operator new functions and operator delete functions, shall refer to the same function, or to a function defined within the definition of D; […]

What this effectively means is that any functions called in the entity's definition shall be the same in all translation units - or have been defined inside its definition, like local classes and their members. I.e. usage of a lambda per se is not problematic, but passing it to function templates clearly is, since these are defined outside the definition.

In your example with C, the closure type is defined within the class (whose scope is the smallest enclosing one). If the closure type differs in two TUs, which the standard may unintentionally imply with the uniqueness of a closure type, the constructor instantiates and calls different specializations of function's constructor template, violating (6.4) in the above quote.

curiousguy
  • 8,038
  • 2
  • 40
  • 58
Columbo
  • 60,038
  • 8
  • 155
  • 203
  • 1
    Comments are not for extended discussion; this conversation has been [moved to chat](http://chat.stackoverflow.com/rooms/100403/discussion-on-answer-by-columbo-does-using-lambda-in-header-file-violate-odr). – George Stocker Jan 11 '16 at 18:20
  • 3
    @GeorgeStocker Where are the comments? – Columbo Jan 14 '16 at 17:46
  • 1
    I figured you would have gotten the hint the first time I said 'comments are not for extended discussion'. Sadly, the software only lets me move comments to chat once. I suppose it's that way because if I have to do that multiple times, once wasn't enough; and probably should have been. – George Stocker Jan 14 '16 at 18:01
  • 8
    @GeorgeStocker If it couldn't have been moved, you should've left it as it was. Please don't delete valuable discussions; this was one. – Columbo Jan 15 '16 at 21:31
  • 1
    @columbo comments aren't for extended discussions. If you want to retain knowledge for future people to read, put it in your answer. It logically flows there. Or, have that person ask a new question so you can put your knowledge in an answer. We are not a discussion forum; our format doesn't do that well and it detracts from the user experience. – George Stocker Jan 15 '16 at 23:09
  • 11
    @GeorgeStocker If it were that simple, why isn't there an automated process that blindly deletes all comments when the list gets too long, the way you did? We're all programmers, we all know how trivial that would be to implement. Why does the site require manual intervention from a moderator in such cases? Perhaps because moderators are expected to *use their judgment* when doing this, precisely in order to avoid losing valuable content? Regarding "put it in your answer": did you give Columbo a chance to move the content into the answer before deleting it? – bogdan Jan 16 '16 at 11:02
  • 2
    I am just so mad that the comments got removed as some one who just chanced up on this discussion. Who in the world would do a ham handed thing like that? – ForeverLearning Feb 17 '16 at 22:37
8

UPDATED

After all I agree with @Columbo answer, but want to add the practical five cents :)

Although the ODR violation sounds dangerous, it's not really a serious problem in this particular case. The lambda classes created in different TUs are equivalent except their typeids. So unless you have to cope with the typeid of a header-defined lambda (or a type depending on the lambda), you are safe.

Now, when the ODR violation is reported as a bug, there is a big chance that it will be fixed in compilers that have the problem e.g. MSVC and probably some other ones which don't follow the Itanium ABI. Note that Itanium ABI conformant compilers (e.g. gcc and clang) are already producing ODR-correct code for header-defined lambdas.

oliora
  • 839
  • 5
  • 12