【问题标题】:How can I create a macro which uses a value multiple times, without copying it?如何创建一个多次使用一个值而不复制它的宏?
【发布时间】:2015-09-25 04:26:43
【问题描述】:

我想创建一个宏,将一对解包成两个局部变量。如果它只是一个变量,我不想创建该对的副本,这将完成:

#define UNPACK_PAIR(V1, V2, PAIR) \
    auto& V1 = PAIR.first; \
    auto& V2 = PAIR.second;

UNPACK_PAIR(one, two, x);

但是,我也希望它不计算多次给出的表达式,例如这应该只调用一次expensive_computation()

UNPACK_PAIR(one, two, expensive_computation());

如果我这样做:

#define UNPACK_PAIR_A(V1, V2, PAIR) \
    auto tmp = PAIR; \
    auto& V1 = tmp.first; \
    auto& V2 = tmp.second;

那么它适用于expensive_computation() 案例,但它会在x 案例中复制。如果我这样做:

#define UNPACK_PAIR_R(V1, V2, PAIR) \
    auto& tmp = PAIR; \
    auto& V1 = tmp.first; \
    auto& V2 = tmp.second;

然后它在x 的情况下工作而无需复制,但在expensive_computation() 的情况下失败。如果我这样做:

#define UNPACK_PAIR_CR(V1, V2, PAIR) \
    const auto& tmp = PAIR; \
    auto& V1 = tmp.first; \
    auto& V2 = tmp.second;

#define UNPACK_PAIR_RR(V1, V2, PAIR) \
    auto&& tmp = PAIR; \
    auto& V1 = tmp.first; \
    auto& V2 = tmp.second;

它们都可以编译和运行,但我怀疑它们会调用未定义的行为——我对此是否正确?另外,其中任何一个都有意义吗?

#define UNPACK_PAIR_RR(V1, V2, PAIR) \
    auto&& tmp = std::move(PAIR); \
    auto& V1 = tmp.first; \
    auto& V2 = tmp.second;

#define UNPACK_PAIR_RR(V1, V2, PAIR) \
    auto&& tmp = std::forward<decltype(PAIR)>(PAIR); \
    auto& V1 = tmp.first; \
    auto& V2 = tmp.second;

是否有任何方法可以创建适用于这两种用例的宏 - 既不复制 x,也不在给定表达式或函数调用的结果时调用未定义的行为?

【问题讨论】:

  • “这些都可以编译和运行,但我怀疑它们会调用未定义的行为 - 我对此是否正确?” 这取决于用法。它为auto id = [](auto&amp;&amp; x) -&gt; decltype(auto) { return decltype(x)(x); }; auto&amp;&amp; tmp = id(5); 之类的东西调用UB,但它不为auto&amp;&amp; tmp = 5; 调用UB——这与绑定到引用的临时对象的生命周期的延长有关。您是否需要通过宏保持值 V 有效? auto tmp = V; 也保持其价值,除非它存在内部生命周期问题。
  • @dyp:是的,我想保持该值有效,而不复制它(如果给定一个局部变量,auto tmp = V 会这样做)
  • This 可能是相关的。
  • 我真的不清楚你想做什么。你想获得对中每个成员的引用吗?为什么?为了什么?有什么限制?为什么是宏?
  • 用例是什么?您想为.first.second 成员取更好的名字吗?您想通过更短的名称更轻松地访问吗?

标签: c++ c++11 macros undefined-behavior forwarding-reference


【解决方案1】:

你不需要宏。

auto p = std::make_pair(2, 3);
int x, y;
std::tie(x, y) = p;

如果您想引用一对现有成员:

auto p = std::make_pair(2, 3);
auto& x = p.first;
auto& y = p.second;

就是这样。

现在你可以继续做一些更具挑战性/有趣/重要的事情了。

【讨论】:

  • 除非类型不是默认可构造的。
  • @Claudiu 我理解,但是试图模仿另一种语言及其习语会导致不良的编码习惯,导致代码不可读/不可维护,并且(如前所述)通常没有意义(另外,一般情况下是不可能的)。学习新语言,而不是试图“模拟”你最喜欢的语言。
  • Python 不要求您在使用变量之前声明它们。 C++ 可以。因此,C++ 的元组解包要求您首先声明变量也就不足为奇了。
  • @Claudiu 请问您为什么没有用 Python 编写这个特定的程序,因为它似乎具有您正在寻找的所有功能?
  • 他忘记了动作、RVO 以及其他所有事情
【解决方案2】:

auto&amp;&amp; 创建一个转发引用,即它接受任何内容。它确实 not (总是)创建一个右值引用。所以就这样做吧:

#define UNPACK_PAIR(V1, V2, PAIR) \
    auto&& tmp = PAIR; \
    auto& V1 = tmp.first; \
    auto& V2 = tmp.second;

但是,我强烈建议不要这样做(除非UNPACK_PAIR 的使用范围非常有限,并且该操作在该范围内真的无处不在)。它看起来默默无闻,对我没有真正的好处。想象一下在 6 个月后回到项目,只需要两个小时就可以找到一个严重的错误。你会感谢自己使用非标准的基于宏的语法而不是可读的东西吗?

【讨论】:

  • 请注意,对于某些极端情况,这可能会导致生命周期问题(如果 PAIR 是一个返回不直接绑定到 tmp 的临时的表达式)。
  • @dyp 你能举个例子吗?
  • @Claudiu 是的,该临时文件的生命周期(get_foo() 的返回值)将被延长。
  • 这会用tmp 污染当前范围;例如在一个范围内使用宏两次会导致错误。
  • @MattMcNabb:没错,你必须使用一个宏来生成一个唯一的可重复使用的临时变量(使用 __LINE__ 的东西),然后一切都会解决。
【解决方案3】:

你想要的是std::tie

decltype(p.first) x;
decltype(p.second) y;
std::tie(x,y) = p;

如果你愿意,你甚至可以用它来定义你的宏。请注意,这仅适用于 2 元组 - 如果您想要 3 元组或更多,您需要做一些不同的事情。例如,如果你有一个三元组t

decltype(std::get<0>(t)) x;
decltype(std::get<1>(t)) y;
decltype(std::get<2>(t)) z;
std::tie(x,y,z) = t;

【讨论】:

  • 这将复制值,不过(假设该对不存储引用)。而且它不必要地不适用于非默认可构造类型。 decltype(p.first)&amp; x = p.first; 等在 IMO 中更有意义,但这与其他人提出的(更好的)auto&amp; x = p.first; 非常接近。
  • 是的,在这里使用decltype 的唯一原因是您不必立即初始化它。否则auto 更胜一筹。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-03-26
  • 1970-01-01
  • 2021-05-21
  • 2012-04-27
  • 2014-08-15
  • 1970-01-01
  • 2019-09-07
相关资源
最近更新 更多