【问题标题】:Lack of type information in structured bindings结构化绑定中缺少类型信息
【发布时间】:2021-12-05 16:51:08
【问题描述】:

我刚刚了解了 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 的返回类型不兼容的东西时,编译器最好抱怨,即不是const auto /* T1, T2 */ [x, y] = some_func();


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

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

//
// 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 的声明在我的脑海中是新鲜的,所以我知道它们的返回类型。但是一周后看Program.cpp时,我几乎不记得main中的代码,更不用说它的变量的数据类型或者get_valuesget_objects的返回类型,所以我需要打开API.cpp并找到get_valuesget_objects 了解它们的返回类型。

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

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

【问题讨论】:

  • 编译器抱怨...只是static_assert(std::is_same_v?
  • const auto&amp; t = some_func(); const T1 x = std::get&lt;0&gt;(t); const T2 y = std::get&lt;1&gt;(t); 没有比这更明确的了。
  • "...我必须查找 some_func 的声明..." 如果您不查找声明,那么您怎么知道 auto [x, y] = some_func(); 在第一名?
  • @RichardCritten 我认为关键在于何时回读代码。尽管如此,这并不重要......
  • 使用auto 的缺点与不使用c++11 - Is there a downside to declaring variables with auto in C++? - Stack Overflow 相同(另请参阅其链接问题。)

标签: c++ c++17 structured-bindings


【解决方案1】:

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

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

理解这一点,现在考虑以下语句:int i = expr; 只要expr 可以转换int,此代码就可以工作。

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

但这很危险,尤其是在处理包含引用的返回值时。理论上,您可以将tuple&lt;float&amp;, int&gt; 转换为tuple&lt;int, int&gt;。但它不会有相同的含义,因为你不能修改被引用的对象。

但同样,用户期望变量声明能够进行这样的转换。用户一直依赖它。取消该功能只会造成功能混乱。

所以该功能不会这样做。

【讨论】:

  • 非常有见地。我考虑了结构化绑定,例如从 Python 中解包元组,其中元组的项被“解包”并分发到赋值左侧的变量中。但是如果结构化绑定中的“变量”只是编译器在后台声明的元组元素的别名,那么很明显我不能重新声明变量。
【解决方案2】:

如果您想保留结构化绑定和随之而来的可能优化,最简单的方法是添加表示类型的注释。显然,如果返回类型发生变化,那就不好了:注释会产生误导。手动编写类型时,这会导致编译时错误。
为了模仿这种行为,您可以手动强制编译时错误:

#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。

【讨论】:

    【解决方案3】:

    你可以这样做:

    #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&amp; y 而不是 const double&amp; y )。

    【讨论】:

      猜你喜欢
      • 2019-04-26
      • 1970-01-01
      • 1970-01-01
      • 2018-12-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多