Может ли один элемент кортежа ссылаться на другой?

Обновление:Смотрите полный ответ ниже. Короткий ответ — нет, не напрямую. Вы можете создать косвенную ссылку, используя std::reference_wrapper, или достичь того же эффекта в более общем смысле с помощью указателей (, но без синтаксического сахара и дополнительной безопасности ссылок ).

Я спрашиваю, потому что кортежи представляют собой удобную единицу хранения с переменным числом аргументов в C++11. Теоретически кажется разумным, чтобы один элемент кортежа содержал ссылку на другой элемент того же кортежа. (Замените «ссылку» на «указатель», и это сработает на практике. )Дьявол кроется в деталях построения такого кортежа. Рассмотрим следующий пример:

#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;
}

Второй элемент кортежа t1ссылается на автоматическую переменную aвместо первого элемента в t1.Есть ли способ построить кортеж так, чтобы один элемент кортежа мог содержать ссылку на другой элемент в том же кортеже? Я знаю, что вы могли бы добиться такого результата, создав кортеж ссылок, например:

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;
}

Но для моих целей это мошенничество, поскольку второй элемент в 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?
}

Редактировать :Вот минимальная тестовая программа, которая возвращает не -нулевой статус выхода при компиляции с использованием g++ 4.7 с -O2 или выше. Это предполагает либо неопределенное поведение, либо ошибку в 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 );
}
8
задан Benjamin Kay 29 July 2012 в 03:40
поделиться