4

私のコードでは、いくつかのテンプレート パラメーターに依存するラムダ式を含む関数テンプレートを取得しています。最近、おそらく g++ コンパイラの更新が原因で、リンカ エラーが発生しましたが、残念ながら正確にはわかりません。

この問題を示す小さな例を挙げます。これはリンカーの問題であるため、デモ用にいくつかのファイルを作成する必要があります。共通common.hppのテンプレート関数を含む 、その関数を利用するa.cpp/a.hppb.cpp/の 2 つのモジュール、および関数を含むモジュールがあります。b.hppmain.cppmain

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

基本的に、ラムダ式はすでにaで定義されていると言います。わかった。テンプレート パラメータをb5 から 7 に変更すると、すべてが機能します。

質問:

  1. これは私が期待すべきことですか、それとも のバグg++ですか? 私はこのコードを以前のバージョンの の debian パッケージでコンパイルしたことを確信していますg++-4.7
  2. ラムダを使用しない以外に回避策はありますか? 結果のシンボルが静的であれば、たとえば問題はないと思います。更新: 回避策: my_transformstaticまたはinline.

この質問はあまり重要ではありません。「ここではラムダを使用しない」というアプローチで問題はありませんが、私は興味があります。:)

4

2 に答える 2

4

これは、4.7.0-11 と4.7.0-12の間に導入された g++ のバグです(これら 2 つの 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、それが非静的関数テンプレートであり、要件を満たしていることがわかります (簡潔にするために省略されています)。そのため、プログラムはプログラム全体で定義が 1 つしかないかのように動作するはずです。それはその中に何があるかに関係なく成り立つのでoperator()、ラムダの がそうであるかそうでないかは問題ではありませんinline(そうあるべきですが、実際には1は問題ではありません)。

ところで、貧しい人のラムダ関数と同等です(実際には同等であるはずです)。

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 の対象になるかどうかはわかりません (cf. 5.1.2/3 および 3.5/8)。

念のために言っておきますが、このバグは commit f899a730d4f41b6a20b5508059a450f3a9347316によって導入されました。

于 2012-06-07T22:26:34.803 に答える
1

これは、コンパイラ/リンカーの問題4.6.1にないようです。

このコマンドはシステムに何を出力しますか?

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

4.6.1では、1つの弱いシンボル(のインスタンス化std::transform)と1つのローカルシンボル(operator()ラムダの場合)があります。operator int (*)(int)() const(問題の原因と思われるもの)はまったく定義されていません。

于 2012-06-07T21:39:36.363 に答える