结构化绑定中缺少类型信息

Lack of type information in structured bindings

我刚刚了解了 C++ 中的结构化绑定,但有一点我不喜欢

auto [x, y] = some_func();

auto隐藏了xy的类型。我必须查看 some_func 的声明才能知道 xy 的类型。或者,我可以写

T1 x;
T2 y;
std::tie(x, y) = some_func();

但这只适用于 xy 是默认可构造的而不是 const 的情况。有没有办法写

const auto [x, y] = some_func();

对于 xy 的非默认可构造类型,使得 xy 的类型可见?当我将 xy 声明为与 some_func 的 return 类型不兼容的东西时,编译器最好会抱怨,即不是 const auto /* T1, T2 */ [x, y] = some_func();.


澄清。 由于我的问题下面的评论似乎围绕着是否使用 &,而之前的一些答案将我的问题误解为“使用哪种语法用于提取 returned 对的数据类型”,我想我需要澄清我的问题。

假设我们的代码分布在多个文件中

//
// API.cpp
//
#include <utility>

class Foo {
public:
    Foo () {}
};

Foo foo;


class Bar {
private:
    Bar () {}
public:
    static Bar create () { return Bar(); }
};

Bar bar = Bar::create();


std::pair<int, bool> get_values () {
    return std::make_pair(73, true);
}

std::pair<Foo&, Bar&> get_objects () {
    return std::pair<Foo&, Bar&>(foo, bar);
}

//
// Program.cpp
//
int main (int, char**) {
    const auto [x, y] = get_values();
    const auto& [foo, bar] = get_objects();

    /* Do stuff with x, y, foo and bar */

    return 0;
}

在编写这段代码时,get_valuesget_objects 的声明在我的脑海中是新鲜的,所以我知道它们的 return 类型。但是当一周后查看 Program.cpp 时,我几乎不记得 main 中的代码,更不用说它的变量的数据类型或 get_values 和 [=34= 的 return 类型了], 所以我需要打开 API.cpp 并找到 get_valuesget_objects 以了解它们的 return 类型。

我的问题是在main中是否有语法来写变量xyfoobar的数据类型进入结构化绑定?最好以一种允许编译器纠正我的方式,如果我犯了错误,那么没有评论。类似于

int main (int, char**) {
    // Pseudo-Code
    [const int x, const bool y] = get_values();
    [const Foo& foo, const Bar& bar] = get_objects();
    /* Do stuff with x, y, foo and bar */
    return 0;
}

如果你想保留结构化绑定和可能的优化,最简单的方法是添加一个注释来表示类型。显然,如果 return 类型发生变化将很糟糕:注释会产生误导。手动编写类型时,这会导致编译时错误。
要模拟此行为,您可以手动强制编译时错误:

#include <type_traits>

auto [x, y] = some_func();
static_assert(std::is_same_v<decltype(x), const my_type>);
static_assert(std::is_same_v<decltype(y), some_other__type>);

它将显示类型信息,如果类型碰巧不是您期望的类型,则强制出现编译时错误,甚至可以防止发生不需要的转换。不再意外地将 long 分配给 int

或者:使用 IDE 可以显示局部变量及其类型。

没有任何机制可以在结构化绑定声明中声明“变量”的类型。如果您希望类型名称可见,则必须放弃结构化绑定声明的便利性。

这很重要,因为结构化绑定的工作原理。 xy 本身并不是真正的变量。它们是访问结构化绑定语句存储的对象组件的替代方法。它们是声明捕获的对象的组件。只有一个实际变量:auto-推导出的未命名变量。您声明的名称只是该对象的组成部分。

理解了这一点,现在考虑这个陈述:int i = expr; 只要 expr 可以 转换 为 [=15],这段代码就可以工作=].

如果您可以将类型名放在结构化绑定声明中,人们会有 相同 的期望。他们希望如果一个函数 return 是 tuple<float, int>,他们可以在 auto [int x, int y] 中捕获它。但是它们不能,因为被存储的对象是一个tuple<float, int>,它的第一个成员是一个float。编译器必须发明一些包含两个 int 的新对象并进行转换。

但这很危险,尤其是在处理包含引用的 return 值时。理论上你可以把 tuple<float&, int> 变成 tuple<int, int>。但它不会有相同的含义,因为您不能修改被引用的对象。

但同样,用户希望 变量声明能够进行此类转换。用户一直依赖它。剥夺这种权力只会在功能中造成混乱。

因此该功能无法实现。

你可以这样做:

#include <iostream>
#include <tuple>

auto func( )
{
    int x = 5;
    double y = 5.5;

    std::cout << "Executing the func..." << '\n';

    return std::make_tuple<int, double>( std::move( x ), std::move( y ) );
}

int main()
{
    const auto returnVal = func( );
    const int& x = std::get<0>( returnVal );
    // const unsigned int& x = std::get<0>( returnVal ); // replace this with the upper one and 
                                                         // it will provoke a compile-time error

    const double& y = std::get<1>( returnVal );
    // const char& y = std::get<1>( returnVal ); // replace this with the upper one and 
                                                 // it will provoke a compile-time error

    static_assert( std::is_same_v< decltype( x ), decltype( std::get<0>( returnVal ) ) > &&
                   std::is_same_v< decltype( y ), decltype( std::get<1>( returnVal ) ) >, "Wrong types used" );

    std::cout << x << " " << y << '\n';

    return 0;
}

如您所见,这可以替代结构化绑定。 xy 是对返回值的引用。在阅读源代码时,您 明确地 看到了它们的类型。如果你用错误的类型声明它们,那么编译器会抱怨(例如 const char& y 而不是 const double& y )。