C++11 Lambdas and Templates cause strange linker behaviour
-
13-06-2021 - |
Question
In my code, I have got function templates containing lambda expressions which depend on some of the template parameters. Recently I got linker errors, maybe due to an update of my g++-compiler, but unfortunately, I do not know exactly.
I will give a small example that demonstrates the issue. Because it is a linker issue, we have to create a couple of files to demonstrate it. We have common.hpp
, which contains a common template function, two modules a.cpp
/a.hpp
and b.cpp
/b.hpp
making use of that function and a main.cpp
module containing the main
function.
// common.hpp
#include <algorithm>
template <class Iterator, typename Iterator::value_type x>
void
my_transform(Iterator begin, Iterator end)
{
std::transform(begin, end, begin,
[] (typename Iterator::value_type y) { return x+y; });
}
File a.cpp
:
// a.cpp
#include "common.hpp"
#include "a.hpp"
void a(std::vector<int>& vec)
{
my_transform<std::vector<int>::iterator, 5>(vec.begin(), vec.end());
}
File a.hpp
#include <vector>
void a(std::vector<int>& vec);
File b.cpp
:
// b.cpp
#include "common.hpp"
#include "b.hpp"
void b(std::vector<int>& vec)
{
my_transform<std::vector<int>::iterator, 5>(vec.begin(), vec.end());
}
File b.hpp
#include <vector>
void b(std::vector<int>& vec);
File main.cpp
int main() { return 0; }
If I compile and link using
g++-4.7 -std=c++11 -c a.cpp
g++-4.7 -std=c++11 -c b.cpp
g++-4.7 -std=c++11 -c main.cpp
g++ a.o b.o main.o
I get a multiple-definition
error:
b.cpp:(.text+0x30): multiple definition of `void my_transform<__gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, 17>(__gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, __gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >)::{lambda(int)#1}::operator int (*)(int)() const'
a.o:a.cpp:(.text+0x30): first defined here
Basically it says that the lambda expression was already defined in a. Okay. If I change the template parameter in b
from 5 to 7, everything works.
Questions:
- Is this what I should expect or is it a bug in
g++
? I am very sure that I compiled this code with an earlier version of the debian package ofg++-4.7
. - Are there any workarounds apart from not using lambdas? If the resulting symbols were static there wouldn't be any problems for example, I think. Update: Workaround: Make my_transform
static
orinline
.
This question is not very important. There is no problem with the "do not use lambdas here" approach, but I am curious. :)
Solution
It is a bug in g++, which was introduced between 4.7.0-11 and 4.7.0-12 (I tested those two Debian versions). gcc-snapshot (20120601-1) is fine, too, unfortunately, I don't know what are the differences between those too - 4.7.0-12 is 6 days later and a different branch, and I don't have a gcc repository here to compare). I couldn't find the relevant entry in gcc's bugzilla.
The relevant section of the standard is
There can be more than one definition of a class type (Clause 9), enumeration type (7.2), inline function with external linkage (7.1.2), class template (Clause 14), non-static function template (14.5.6), static data member of a class template (14.5.1.3), member function of a class template (14.5.1.1), or template specialization for which some template parameters are not specified (14.7, 14.5.5) in a program provided that each definition appears in a different translation unit, and provided the definitions satisfy the following requirements ... then the program shall behave as if there were a single definition of D [D is that class/function/whatever].
When you apply this paragraph to my_transform
, you see that it is a non-static function template, it satisfies the requirements (omitted for brevity), so the program should behave as if there was only one definition in the whole program. That holds regardless of what is inside it, so it doesn't matter if the operator()
of the lambda is or isn't inline
(it should be, but doesn't matter really1).
BTW the poor man's lambda function equivalent (which should really be equivalent AFAIK)
template <class Iterator, typename Iterator::value_type x>
void
my_transform(Iterator begin, Iterator end)
{
struct Foo {
auto operator()(typename Iterator::value_type y) const -> decltype(x+y) { return x+y; }
};
std::transform(begin, end, begin,
Foo());
}
still works.
1: I'm not sure if it itself could be subject to ODR, since it has no linkage (cf. 5.1.2/3 and 3.5/8)
Just to be sure I don't forget it, the bug was introduced by commit f899a730d4f41b6a20b5508059a450f3a9347316
OTHER TIPS
This looks like a compiler/linker issue 4.6.1 does not have it.
What does this command output on your system?
nm -C a.o | grep 'lambda(int)#1'
?
With 4.6.1, there's one weak symbol (an instantiation of std::transform
) and one local symbol (operator()
for the lambda). There's no operator int (*)(int)() const
(what appears to cause the problem) defined at all.