结构化绑定中缺少类型信息
Lack of type information in structured bindings
我刚刚了解了 C++ 中的结构化绑定,但有一点我不喜欢
auto [x, y] = some_func();
是auto
隐藏了x
和y
的类型。我必须查看 some_func
的声明才能知道 x
和 y
的类型。或者,我可以写
T1 x;
T2 y;
std::tie(x, y) = some_func();
但这只适用于 x
和 y
是默认可构造的而不是 const
的情况。有没有办法写
const auto [x, y] = some_func();
对于 x
和 y
的非默认可构造类型,使得 x
和 y
的类型可见?当我将 x
和 y
声明为与 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_values
和 get_objects
的声明在我的脑海中是新鲜的,所以我知道它们的 return 类型。但是当一周后查看 Program.cpp
时,我几乎不记得 main
中的代码,更不用说它的变量的数据类型或 get_values
和 [=34= 的 return 类型了], 所以我需要打开 API.cpp
并找到 get_values
和 get_objects
以了解它们的 return 类型。
我的问题是在main
中是否有语法来写变量x
、y
、foo
和bar
的数据类型进入结构化绑定?最好以一种允许编译器纠正我的方式,如果我犯了错误,那么没有评论。类似于
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 可以显示局部变量及其类型。
没有任何机制可以在结构化绑定声明中声明“变量”的类型。如果您希望类型名称可见,则必须放弃结构化绑定声明的便利性。
这很重要,因为结构化绑定的工作原理。 x
和 y
本身并不是真正的变量。它们是访问结构化绑定语句存储的对象组件的替代方法。它们是声明捕获的对象的组件。只有一个实际变量: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;
}
如您所见,这可以替代结构化绑定。 x
和 y
是对返回值的引用。在阅读源代码时,您 明确地 看到了它们的类型。如果你用错误的类型声明它们,那么编译器会抱怨(例如 const char& y
而不是 const double& y
)。
我刚刚了解了 C++ 中的结构化绑定,但有一点我不喜欢
auto [x, y] = some_func();
是auto
隐藏了x
和y
的类型。我必须查看 some_func
的声明才能知道 x
和 y
的类型。或者,我可以写
T1 x;
T2 y;
std::tie(x, y) = some_func();
但这只适用于 x
和 y
是默认可构造的而不是 const
的情况。有没有办法写
const auto [x, y] = some_func();
对于 x
和 y
的非默认可构造类型,使得 x
和 y
的类型可见?当我将 x
和 y
声明为与 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_values
和 get_objects
的声明在我的脑海中是新鲜的,所以我知道它们的 return 类型。但是当一周后查看 Program.cpp
时,我几乎不记得 main
中的代码,更不用说它的变量的数据类型或 get_values
和 [=34= 的 return 类型了], 所以我需要打开 API.cpp
并找到 get_values
和 get_objects
以了解它们的 return 类型。
我的问题是在main
中是否有语法来写变量x
、y
、foo
和bar
的数据类型进入结构化绑定?最好以一种允许编译器纠正我的方式,如果我犯了错误,那么没有评论。类似于
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 可以显示局部变量及其类型。
没有任何机制可以在结构化绑定声明中声明“变量”的类型。如果您希望类型名称可见,则必须放弃结构化绑定声明的便利性。
这很重要,因为结构化绑定的工作原理。 x
和 y
本身并不是真正的变量。它们是访问结构化绑定语句存储的对象组件的替代方法。它们是声明捕获的对象的组件。只有一个实际变量: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;
}
如您所见,这可以替代结构化绑定。 x
和 y
是对返回值的引用。在阅读源代码时,您 明确地 看到了它们的类型。如果你用错误的类型声明它们,那么编译器会抱怨(例如 const char& y
而不是 const double& y
)。