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:

  1. 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 of g++-4.7.
  2. 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 or inline.

This question is not very important. There is no problem with the "do not use lambdas here" approach, but I am curious. :)

Was it helpful?

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top