8

更新:以下の完全な回答を参照してください。簡単な答えは、直接ではなく、いいえです。間接参照を使用してstd::reference_wrapper、またはより一般的にポインターを使用して同じ効果を達成することができます(ただし、構文糖衣構文および参照の安全性の追加はありません)。

タプルはC++11で便利な可変個引数ストレージユニットを作成するためです。理論的には、タプルの1つの要素が、同じタプル内の別の要素への参照を保持することは合理的に聞こえます。(「参照」を「ポインター」に置き換えれば、実際に機能します。)悪魔はそのようなタプルを構築する詳細です。次の例を考えてみましょう。

#include <tuple>
#include <iostream>

class A
{
public:
  A() : val(42) { }
  int val;
};

class B
{
public:
  B(A &a) : _a(a) { }
  int val() { return _a.val; }

private:
  A &_a;
};

int main()
{
  A a;
  B b(a);
  std::tuple<A, B> t1(a, b);
  a.val = 24;
  std::cout << std::get<0>(t1).val << "\n"; // 42
  std::cout << std::get<1>(t1).val() << "\n"; // 24

  return 0;
}

タプルの2番目の要素は、の最初の要素ではなくt1、自動変数を参照します。タプルの1つの要素が同じタプル内の別の要素への参照を保持できるようにタプルを構築する方法はありますか?次のような参照のタプルを作成することで、この結果を達成できることを認識しています。at1

int main()
{
  A a;
  B b(a);
  std::tuple<A &, B &> t2 = std::tie(a, b);
  a.val = 24;
  std::cout << std::get<0>(t2).val << "\n"; // 24
  std::cout << std::get<1>(t2).val() << "\n"; // 24

  return 0;
}

しかし、私の目的では、それは不正行為です。なぜなら、の2番目の要素は、t2最終的にはタプルの外部にあるオブジェクトを参照しているからです。私がそれを行うことを考えることができる唯一の方法はうまくコンパイルされますが、未定義の動作が含まれている可能性があります[ハワード・ヒナントによって提供されたより簡潔な例を反映するように編集されました]:

int main()
{
    std::tuple<A, B> t3( A(), B(std::get<0>(t3)) ); // undefined behavior?
    std::get<0>(t3).val = 24;
    std::cout << std::get<0>(t3).val << "\n";
    std::cout << std::get<1>(t3).val() << "\n"; // nasal demons?
}

編集:これは、-O2以上のg++4.7を使用してコンパイルしたときにゼロ以外の終了ステータスで戻る最小限のテストプログラムです。これは、未定義の動作またはgccのバグのいずれかを示唆しています。

#include <tuple>

class Level1
{
public:
  Level1() : _touched(false), _val(0) { }

  void touch()
  {
    _touched = true;
  }

  double feel()
  {
    if ( _touched )
    {
      _touched = false;
      _val = 42;
    }
    return _val;
  }

private:
  bool _touched;
  double _val;
};

class Level2
{
public:
  Level2(Level1 &level1) : _level1(level1) { }

  double feel()
  {
    return _level1.feel();
  }

private:
  int _spaceholder1;
  double _spaceholder2;
  Level1 &_level1;
};

class Level3
{
public:
  Level3(Level2 &level2) : _level2(level2) { }

  double feel()
  {
    return _level2.feel();
  }

private:
  Level2 &_level2;
};

int main()
{
  std::tuple<Level3, Level2, Level1> levels(
    Level3(std::get<1>(levels)),
    Level2(std::get<2>(levels)),
    Level1()
  );

  std::get<2>(levels).touch();

  return ! ( std::get<0>(levels).feel() > 0 );
}
4

2 に答える 2

5

これは私のために働く:

#include <tuple>
#include <iostream>

int main()
{
    std::tuple<int&, int> t(std::get<1>(t), 2);
    std::cout << std::get<0>(t) << '\n';
    std::get<1>(t) = 3;
    std::cout << std::get<0>(t) << '\n';
}

アップデート

この件について CWG メーリング リストで質問しました。Mike Miller は、これ3.8p6 の箇条書き 2 による未定義の動作であることを保証します。

... 次の場合、プログラムは未定義の動作をします。

...

  • glvalue は、非静的データ メンバーにアクセスするか、オブジェクトの非静的メンバー関数を呼び出すために使用されます。

...

tuple集約であれば明確に定義された動作ですがtuple、ユーザー宣言のコンストラクターがあるため、3.8p6b2 が適用されます。

ただし、これは機能し、UB を回避します。

#include <tuple>
#include <functional>
#include <cassert>

int main()
{
    int dummy;
    std::tuple<std::reference_wrapper<int>, int> t(dummy, 2);
    std::get<0>(t) = std::get<1>(t);
    assert(std::get<0>(t) == 2);
    std::get<1>(t) = 3;
    assert(std::get<0>(t) == 3);
}
于 2012-07-26T18:41:24.007 に答える
0

私が知る限り、まだ存在しないオブジェクトにエイリアスを設定することは明確に定義された動作です。これは、コンストラクターの初期化リストで使用されているのと同じ状況ですthis。これは残念ですが、一般的で明確に定義されています。

于 2012-07-26T18:44:45.660 に答える