【问题标题】:Perfect forwarding of variables declared with structured binding使用结构化绑定声明的变量的完美转发
【发布时间】:2017-05-27 17:37:02
【问题描述】:

我有一个结构

template <typename T>
struct Demo {
    T x;
    T y;
};

我正在尝试为元组编写一个类似于std::get 的通用函数,该函数采用编译时索引I 并返回对结构的I-th 成员的左值引用(如果它被调用)如果使用右值 DemoStruct&lt;T&gt; 调用该结构,则使用左值 DemoStruct&lt;T&gt; 和对结构的 I-th 成员的右值引用。

我目前的实现是这样的

template <size_t I, typename T> 
constexpr decltype(auto) struct_get(T&& val) {
    auto&& [a, b] = std::forward<T>(val);

    if constexpr (I == 0) {
        return std::forward<decltype(a)>(a);
    } else {
        return std::forward<decltype(b)>(b);
    }
}

但是,这并没有达到我的预期,而是总是返回一个对 T 的右值引用。

Here 是一个显示问题的魔杖盒。

返回对结构成员的引用的正确方法是什么?保留传递给函数的结构的值类别?

编辑: 正如 Kinan Al Sarmini 指出的那样,auto&amp;&amp; [a, b] = ... 确实将ab 的类型推导出为非引用类型。 std::tuple 也是如此,例如两者都有

std::tuple my_tuple{std::string{"foo"}, std::string{"bar"}};
auto&& [a, b] = my_tuple;
static_assert(!std::is_reference_v<decltype(a)>);

std::tuple my_tuple{std::string{"foo"}, std::string{"bar"}};
auto&& [a, b] = std::move(my_tuple);
static_assert(!std::is_reference_v<decltype(a)>);

编译正常,即使std::get&lt;0&gt;(my_tuple) 返回引用,如

std::tuple my_tuple{3, 4};
static_assert(std::is_lvalue_reference_v<decltype(std::get<0>(my_tuple))>);
static_assert(std::is_rvalue_reference_v<decltype(std::get<0>(std::move(my_tuple)))>);

这是 GCC 和 Clang 中的语言缺陷、故意还是错误?

【问题讨论】:

  • ab 被推导出为非引用类型,这就是您收到右值引用的原因。我不确定为什么会这样。
  • 我不明白你的困惑。只要val 是一个引用,其成员的任何“移动”都会对调用对象产生影响。为什么不按照std::get 的示例提供另一个重载,它采用const T&amp; 而不是move
  • @DeiDei std::forward 在提供左值引用类型时不应该返回右值引用,如果与 auto&amp;&amp; 的结构化绑定遵循常规参数推导规则,decltype(a) 应该是。如果我为T const&amp; 创建另一个重载,因为它在演绎上下文中,对于非常量左值,T&amp;&amp; 重载仍将优于T const&amp; 版本。所以我必须为所有 cv 限定版本创建重载,即T&amp;T const&amp;T volatile&amp;T const volatile&amp;,以便它可以处理各种左值;这是很多代码重复。
  • 您是否尝试过使用元组或手动结构化绑定?即这是特定于结构的自动结构化绑定吗?
  • @Yakk 这似乎并不特定于带有结构的结构化绑定。我已经用更多信息更新了这个问题。

标签: c++ language-lawyer c++17 perfect-forwarding structured-bindings


【解决方案1】:

行为正确。

decltype 应用于结构化绑定返回引用类型,对于普通结构,它是引用的数据成员的声明类型(但用完整对象的 cv 限定符修饰),对于元组-类似的情况是“为该元素返回的任何tuple_element”。这大致模拟了decltype 应用于普通类成员访问的行为。

除了手动计算所需的类型,我目前想不出其他任何方法,即:

using fwd_t = std::conditional_t<std::is_lvalue_reference_v<T>,
                                 decltype(a)&,
                                 decltype(a)>;
return std::forward<fwd_t>(a);

【讨论】:

  • " 给定由 std::tuple_element::type 指定的类型 Ti,每个 vi 都是用初始化器初始化的“对 Ti 的引用”类型的变量,其中引用如果初始值设定项是左值,则为左值引用,否则为右值引用;被引用的类型是 Ti。" -- v_is(又名[a,b]不是引用如何?如果a 表示为v_1 并且标准似乎说v_i 是右值或左值引用,decltype(a) 怎么可能不是参考?
  • @Yakk 它们是(对于类似元组的情况),但 decltype 假装它们不是。
  • int a = 7; int&amp; x = a; static_assert(std::is_same&lt;decltype(x), int&amp;&gt;{}, "wut?");? a 是左值引用类型的标识符。 decltype 应该将其视为标识符,为什么它可以“假装不是”?为什么和上面的x不一样?
  • @Yakk 我的意思是decltype 假装this particular kind of references 不是引用,不是所有引用。
  • 啊,他们到底为什么要这么做?啊,因为.2。明白了。
【解决方案2】:

这是获得您想要的东西的一般解决方案。它是std::forward 的变体,它允许其模板参数和函数参数具有不相关的类型,并且如果其模板参数不是左值引用,则有条件地将其函数参数转换为右值。

template <typename T, typename U>
constexpr decltype(auto) aliasing_forward(U&& obj) noexcept {
    if constexpr (std::is_lvalue_reference_v<T>) {
        return obj;
    } else {
        return std::move(obj);
    }
}

我将其命名为 aliasing_forward,以向 std::shared_ptr 的“别名构造函数”致敬。

你可以这样使用它:

template <size_t I, typename T> 
constexpr decltype(auto) struct_get(T&& val) {
    auto&& [a, b] = val;

    if constexpr (I == 0) {
        return aliasing_forward<T>(a);
    } else {
        return aliasing_forward<T>(b);
    }
}

DEMO

【讨论】:

  • @KonstantinVladimirov 有一个proposal 可以将其添加到标准中,名称为std::forward_like
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-02-17
  • 1970-01-01
  • 2010-11-01
相关资源
最近更新 更多