C ++ 11 Lambdas and Templates cause weird linker behavior

In my code, I have functional templates containing lambda expressions that depend on some template parameters. Recently, I got linker errors, possibly due to updating my g ++ compiler, but unfortunately I don’t know for sure.

I will give a small example demonstrating the problem. Since this is a linker problem, we need to create a couple of files to demonstrate this. We have common.hpp that contains the generic template function, two modules a.cpp / a.hpp and b.cpp / b.hpp using this function and the 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; }); } 

a.cpp file:

 // 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()); } 

a.hpp file

 #include <vector> void a(std::vector<int>& vec); 

b.cpp file:

 // 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()); } 

b.hpp file

 #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++ ao bo 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' ao:a.cpp:(.text+0x30): first defined here 

This basically suggests that the lambda expression is already defined in a. Good. 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 g++-4.7 package g++-4.7 .
  • Are there any workarounds besides using lambda? I think if the received characters were static, there would be no problems. Update: Workaround: Make my_transform static or inline .

This question is not very important. There is no problem with the “don't use lambda here” approach, but I'm curious. :)

+4
source share
2 answers

This is a bug in g ++ that was introduced between 4.7.0-11 and 4.7.0-12 (I tested these two versions of Debian), gcc-snapshot (20120601-1) is excellent, unfortunately, I also don’t know what the difference is between them is 4.7.0 -12 after 6 days and another branch, and I don't have a gcc repository here for comparison). I could not find the corresponding entry in gcc bugzilla.

Relevant section of the standard

There can be more than one definition of a class type (section 9), an enumeration type (7.2), a built-in function with external communication (7.1.2), a class template (section 14), a non-static function template (14.5.6), a static element of a class template (14.5.1.3), a 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 the program, provided that each definition appears in a different translation unit and provided that the definitions satisfy the following requirements ... then the program should behave that k, as if there was one definition of D [D is a class / function / all].

When you apply this paragraph to my_transform , you see that this 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. This is done no matter what is inside it, so it doesn't matter whether operator() lambda or not inline (it should be, but it doesn't really matter 1 ).

By the way, the equivalent of a poor person's lambda function (which really should be equivalent to 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 working.

1: I'm not sure that he himself can be an ODR object, since he has no connection (see 5.1.2 / 3 and 3.5 / 8)

Just to make sure that I didn’t forget, the error was introduced by the commit f899a730d4f41b6a20b5508059a450f3a9347316 command

+4
source

It seems that the compiler / linker 4.6.1 problem does not have this.

What does this command output on your system?

 nm -C ao | grep 'lambda(int)#1' 

?

In 4.6.1, there is one weak character (an instance of std::transform ) and one local character ( operator() for lambda). There is no operator int (*)(int)() const (which seems to be causing the problem) at all.

+1
source

Source: https://habr.com/ru/post/1416676/


All Articles