5

I have a header file with a named lambda that I use for measuring the execution time of some functions (the lambda is partially a result of this question How to Write a Lambda Wrapping a Function with Optional Return Value). It resides in a header file that I include from several translation units. This has worked well with g++ 8 and g++ 9. Now when I switch to g++ 10.1 I get an error when linking.

Please check the following reduced example.

Here is the example in Wandbox: https://wandbox.org/permlink/Sizb6txrkW5dkJwT.

File "Lambda.h":

#pragma once

#include <string>

auto measure = [](bool enabled, const std::string& taskName, auto&& function,
        auto&&... parameters) -> decltype(auto)
{
    return std::forward<decltype(function)>(function)(
            std::forward<decltype(parameters)>(parameters)...);
};

File "Test1.cpp":

#include "Lambda.h"

File "Test2.cpp":

#include "Lambda.h"

Then I build like this:

g++ -c Test1.cpp
g++ -c Test2.cpp
g++ -shared -o Test.dll Test1.o Test2.o

Everything works fine up to g++ 9.2, but with g++ 10.1 I get this error message:

ld.exe: Test2.o:Test2.cpp:(.bss+0x0): multiple definition of `measure'; Test1.o:Test1.cpp:(.bss+0x0): first defined here
collect2.exe: error: ld returned 1 exit status

Why? How can I compile my project with g++ 10.1? I use the named lambda like I would use a template function, therefore I need to write my named lambda into a header file to be able to use it anywhere in the project and I cannot somehow separate declaration from definition, right?

I am looking forward to an answer!

max66
  • 65,235
  • 10
  • 71
  • 111
Benjamin Bihler
  • 1,612
  • 11
  • 32
  • Clang 10 and 11 warns about unused parameters (reduced example!!!), but links without an error message. Check the given Wandbox link. – Benjamin Bihler Jun 02 '20 at 11:01
  • Each occurrence of a lambda is a unique anonymous class. Try to declare `int foo` in global scope in one translation unit, declare `char foo` in global scope in another translation unit, and then see how much progress you can make linking the two. Even if it links, this is undefined behavior and violates the ODR. (or try to link `A foo;` and together with `B foo;`, where `A` and `B` are two different classes). – Sam Varshavchik Jun 02 '20 at 11:12

1 Answers1

6

I get an error when linking.

Why?

Because you violate the One Definition Rule by defining the variable multiple times.

and others do not?

Why?

¯\_(ツ)_/¯ Language implementations are not required to diagnose ODR violations.

I use the named lambda like I would use a template function, therefore I need to write my named lambda into a header file to be able to use it anywhere in the project and I cannot somehow separate declaration from definition, right?

Right.

How can I compile my project with g++ 10.1?

Simple solution: Declare the variable inline (C++17 feature).

As simple, but has curious detail that each TU has their own instance: Declare the variable static.

Third solution: The lambda captures nothing, so you might as well define a template function instead.

Fourth solution: Instead of storing the lambda as global variable, write an inline function that returns an instance of the lambda.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • Thank you for your great and comprehensive answer! But could you elaborate on the `static` declaration or give a link to where the effect is described? Doesn't `inline` already mean that each occurrence of `measure` means creating a new instance? So what is the difference between `static` and `inline`? – Benjamin Bihler Jun 03 '20 at 12:51
  • @BenjaminBihler `Doesn't inline already mean that each occurrence of measure means creating a new instance?` No. There is only one instance of an inline variable in the program, which is shared across all translation units. Sure, the compiler will have to assume that each TU being compiled that uses it has the variable is the one that has it, but linker makes sure that only one ends up in the final program. Just like inline functions. – eerorika Jun 03 '20 at 12:57
  • @BenjaminBihler Namespace scope static variables (just like static functions) exist separately in each translation unit. If you take the address of the variable in one TU, and take the address in another, those addresses are different. If you modify the value (your variable is stateless though, so there are no distinct values), then that change is not reflected across TU boundary. These is described in the [basic.link] section of the standard. – eerorika Jun 03 '20 at 12:58