【问题标题】:C++11 Lambdas and Templates cause strange linker behaviourC++11 Lambda 和模板导致奇怪的链接器行为
【发布时间】:2012-06-07 20:42:09
【问题描述】:

在我的代码中,我的函数模板包含依赖于某些模板参数的 lambda 表达式。最近我遇到了链接器错误,可能是由于我的 g++ 编译器的更新,但不幸的是,我不知道具体情况。

我将举一个小例子来说明这个问题。因为这是一个链接器问题,我们必须创建几个文件来演示它。我们有common.hpp,它包含一个通用模板函数,两个模块a.cpp/a.hppb.cpp/b.hpp 使用该函数,还有一个main.cpp 模块包含main 函数。

// 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

// 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

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

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

文件b.hpp

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

文件main.cpp

int main() { return 0; }

如果我编译和链接使用

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

我收到multiple-definition 错误:

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

基本上,它表示 lambda 表达式已经在 a 中定义。好的。如果我将 b 中的模板参数从 5 更改为 7,一切正常。

问题:

  1. 这是我应该期待的还是g++ 中的错误?我很确定我是用早期版本的 debian 软件包 g++-4.7 编译了这段代码。
  2. 除了不使用 lambda 之外,还有其他解决方法吗?我认为,如果生成的符号是静态的,则不会有任何问题。 更新:解决方法:将 my_transform 设为 staticinline

这个问题不是很重要。 “不要在这里使用 lambdas”方法没有问题,但我很好奇。 :)

【问题讨论】:

  • 使 my_transform 内联,您在这里遇到了 ODR。
  • @Dani 不这么认为。函数模板明确允许有多个定义,只要它们本质上是相同的。
  • @Dani:我完全不确定他是否违反了 ODR。该函数是一个模板,这有很大的不同。
  • 这看起来像是编译器端的一个错误,即生成带有 operator() 的 lambda 未标记为内联。
  • 是的,在我看来像是一个编译器错误。

标签: c++ templates c++11 g++ multiple-definition-error


【解决方案1】:

这是 g++ 中的一个错误,在 4.7.0-11 和 4.7.0-12 之间引入(我测试了这两个 Debian 版本)。 gcc-snapshot (20120601-1) 也很好,不幸的是,我也不知道它们之间有什么区别 - 4.7.0-12 是 6 天后和不同的分支,我这里没有 gcc 存储库可以比较) .我在 gcc 的 bugzilla 中找不到相关条目。

标准的相关部分是

类类型(第 9 条)、枚举类型(7.2)、内联函数可以有多个定义 外部链接(7.1.2),类模板(第 14 条),非静态函数模板(14.5.6),静态数据成员 类模板 (14.5.1.3) 的成员函数、类模板的成员函数 (14.5.1.1) 或模板特化 在程序中未指定某些模板参数(14.7、14.5.5),前提是每个定义 出现在不同的翻译单元中,并且只要定义满足以下要求……那么程序的行为就好像有一个 D 的定义[D 是那个类/函数/什么]

当你将此段应用到my_transform 时,你会看到它是一个非静态函数模板,它满足要求(为简洁起见,省略),所以程序应该表现得好像整个过程中只有一个定义程序。不管里面有什么,这都成立,所以 lambda 的 operator() 是不是 inline 都没有关系(应该是,但真的没关系1 )。

顺便说一句,穷人的 lambda 函数等效(实际上应该等效于 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());
}

仍然有效。

1:我不确定它本身是否会受到 ODR 的约束,因为它没有关联(参见 5.1.2/3 和 3.5/8)

为了确保我不会忘记它,该错误是由提交f899a730d4f41b6a20b5508059a450f3a9347316引入的

【讨论】:

  • "这是g++中的一个bug,在4.7.0-11和4.7.0-12之间引入(我测试了这两个版本)。" +1,这是令人印象深刻的具体。
  • 我很失望 - 没有提到究竟是哪个文件/方法导致了这个错误? ;)
  • gcc-4.7.1 即将推出。它可能有一个修复。我在 bugzilla 中找不到任何关于此的内容。
【解决方案2】:

这看起来像是编译器/链接器问题 4.6.1 没有它。

此命令在您的系统上输出什么?

nm -C a.o | grep 'lambda(int)#1'

?

在 4.6.1 中,有一个弱符号(std::transform 的实例化)和一个本地符号(operator() 用于 lambda)。根本没有定义operator int (*)(int)() const(似乎是什么导致了问题)。

【讨论】:

    猜你喜欢
    • 2014-02-16
    • 1970-01-01
    • 1970-01-01
    • 2023-03-05
    • 2018-08-04
    • 1970-01-01
    • 2010-12-11
    • 2011-06-23
    • 1970-01-01
    相关资源
    最近更新 更多