【问题标题】:Can one element of a tuple reference another?元组的一个元素可以引用另一个元素吗?
【发布时间】:2012-07-26 18:25:42
【问题描述】:

更新:请参阅下面的完整答案。简短的回答是否定的,不是直接的。您可以使用std::reference_wrapper 创建间接引用,或者使用指针更一般地实现相同的效果(但没有语法糖和增加引用的安全性)。

我问是因为元组在 C++11 中是一个方便的可变参数存储单元。从理论上讲,元组的一个元素持有对同一元组中另一个元素的引用听起来是合理的。 (将“reference”替换为“pointer”,它在实践中有效。)魔鬼是构造这样一个元组的细节。考虑以下示例:

#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 中的第二个元素最终仍然引用了一个位于元组之外的对象。我能想到的唯一方法编译得很好,但可能包含未定义的行为[编辑以反映 Howard Hinnant 提供的更简洁的示例]:

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

【问题讨论】:

  • @Drise :来自C++11 tag wiki:“请使用 C++ 标记以及 C++11 标记标记有关 C++11 的问题。”:- ]

标签: c++ c++11 tuples undefined-behavior


【解决方案1】:

这对我有用:

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

【讨论】:

  • 他已经清楚地表明它“对他有用”。他想知道是不是UB。你的回答没有回答这个问题。
  • 不过,这是我尝试做的一个更简洁的例子。谢谢!
  • 我可以用一些专业知识证明std::get 只会给元组成员起别名。但是,我没有找到用标准语言的一半引用来确认以我上面描述的方式这样做不是 UB。然而,这里潜伏着一些非常聪明的语言律师,也许他们可以用相关的引用对其进行分类。第 12.1 节是我希望找到此信息的地方...
  • 我看到两个选项: 1. 从 reference_wrapper 切换到指针。 2.接受int&amp;解决方案的UB。我不知道int&amp; 解决方案会让您失败的任何实现。并且有可能(正在打开一个问题)int&amp; 解决方案将来可能会变得明确(当然不能保证)。归根结底,最好的解决方案是编写代码,然后针对您拥有的任何解决方案进行定期单元测试。这样一来,如果测试准备好捕捉假设变坏,即使是未定义的行为也是可以接受的。
  • @BenVoigt:感谢您对 UB 的尊重。我同意不应轻易容忍。事实上,这就是为什么我提供了一个避免它的选项。话虽如此,这是一门工程学科,评估权衡是很常见的。在考虑依赖 UB 时,了解您的编译器及其运行的硬件非常重要。很高兴知道 C++ 标准针对的是您可能不关心的硬件。例如,左移负数是 UB,以防您想在 UNIVAC 1100 上运行。我很乐意将自己限制在 2 的恭维硬件上。
【解决方案2】:

据我所知,为尚不存在的对象设置别名是一种定义明确的行为。这与您在构造函数初始化列表中使用 this 时发现的情况相同,这种情况很不幸,但很常见且定义明确。

【讨论】:

  • alias (并且只有别名​​,不使用)正在构建的对象是明确定义的行为,但在有问题的代码中 std::get 被调用尚未完成构造的参数,并且根据 gettuple 的实现,您可能会导致未定义的行为。
  • 我不相信std::get 的任何可能实现不是简单的别名操作。
猜你喜欢
  • 2018-06-17
  • 2023-03-15
  • 1970-01-01
  • 2023-04-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-13
相关资源
最近更新 更多