是 std::min 返回一个 lval 标准
Is std::min returning an lval standard?
在gcc 4.7.2上,我可以写:
int x = 1, y = 2;
std::min<int&>(x,y) = 3;
编译并最终将 3 分配给 x
.但是我能找到的每个std::min
参考都说它返回了一个const T&
。那么这种行为是否标准呢?
为什么它不合法
C++11 [algorithms.general]/2 中有四个std::min
函数模板:
template<class T>
const T& min(const T& a, const T& b);
template<class T, class Compare>
const T& min(const T& a, const T& b, Compare comp);
template<class T > T min(initializer_list<T> t);
template<class T, class Compare> T min(initializer_list<T> t, Compare comp);
(我不确定是否为了获取指针,可能会有额外的重载。
为了简化表示法,我将从这里开始使用"大陆常量放置":
template<class T>
T const& min(T const& a, T const& b);
当显式提供第一个模板参数时,如 std::min<int&>(x,y)
,所有这些重载都尝试实例化。对于实例化成功的那些,重载解析会选择可行的候选项,然后选择单个最佳匹配项。如果其中任何重载的实例化(替换(在非即时上下文中失败,则会发生错误。
由于您将int&
作为模板参数传递,因此上述所有重载中的T
都将替换为int&
。这可能会导致实例化initializer_list<T>
(如果编译器可以在不实例化此类的情况下确定正确的重载,则不必这样做(。
重载解析必须检查从类型 int
(参数(到 initializer_list<int&>
的转换是否有效。这需要实例化类,以检查是否转换构造函数。(正如我所说,如果编译器足够聪明,它就不必执行此实例化。
我检查过的编译器 - 包括 libstdc++ 和 libc++ 中的 g++4.9 和 clang++3.5 - 报告了 std::min<int&>(x,y)
的错误。此错误是由于尝试实例化initializer_list<int&>
而发生的:有一个成员定义如下
typedef E const* iterator;
其中E
是initializer_list
的模板参数。用E
代替int&
产量
typedef int& const* iterator;
非法形成指向引用的指针。
成员的定义不在std::min
的直接上下文中,因此会发生错误(程序格式不正确(。
变通解决此问题
但是,我们可以通过显式指定要调用的重载来消除此问题。这可以通过使用函数指针来完成(这需要重载解析才能获取指向单个函数的指针(:
using ft = int&(int&,int&);
constexpr auto f = static_cast<ft*>(&std::min<int&>);
auto const min = f(x,y);
这在libstdc++中工作正常,但在libc++中失败。我找不到任何禁止明确向std::min
提供模板参数的文本,也找不到模板参数不得作为引用的任何要求。
它是如何工作的?强制转换显式选择重载。执行一种特殊的过载解析,该解决方案仅选择类型与ft
相同的重载。这可能需要实例化需要initializer_list
的std::min
重载,但它不需要实例化initializer_list<int&>
。当与调用表达式中的隐式重载解析(如 std::min<int&>(x,y)
(进行比较时,请注意,我们不需要在这里从某种类型转换为intializer_list<int&>
:我们只需要检查initializer_list<int&>
(函数第一个参数的类型(是否与int&
(ft
的第一个参数的类型(的类型相同。
因此,我得出的结论是,上述代码格式良好且行为良好。这意味着libc++中存在一个错误。对于那些感兴趣的人,libc++ 将第一个std::min
重载(带有形式 const&
的两个参数(调度到带有谓词/比较器的形式:
template <class _Tp>
inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11
const _Tp&
min(const _Tp& __a, const _Tp& __b)
{
return _VSTD::min(__a, __b, __less<_Tp>());
}
这里的问题是模板参数没有显式传递给另一个std::min
函数,而是推导。但是,推导的类型是int
,而不是int&
。这会导致函数参数类型int const&
与比较器int&
预期类型不匹配:
bool less(int&, int&);
int const& min(int const& x, int const& y)
{
return less(y, x) ? y : x; // cannot bind an lvalue of type `int const`
// to a parameter of type `int&`
}
<小时 />分配给min(x,y)
当我们通过 std::min<int&>
实例化第一个std::min
重载并替换int&
时,会发生引用折叠:
在初始T const&
参数类型中,T
将替换为 int&
。这会产生int& const&
.const&
随int&
折叠到类型 int&
。实例化函数的签名变为:
int& min<int&>(int& a, int& b);
因此,有了上述f
的定义,我们可以这样写:
f(x, y) = 42;
<小时 />值类别
如果函数的返回类型是左值引用,则函数调用表达式是左值。价值类别与恒定性无关。 std::min
在任何情况下都返回一个左值。
- 如何创建一个CMake变量,除非显式重写,否则使用默认值
- 删除一个线程上有数百万个字符串的大型哈希映射会影响另一个线程的性能
- 为什么两个不同的未命名名称空间可以共存于一个cpp文件中
- 运行同一解决方案的另一个项目的项目
- 挂起和取消挂起一个文件DLL
- 用C++中的一个变量定义一个常量
- 函数向量_指针有不同的原型,我可以构建一个吗
- 在c++中用vector填充一个简单的动态数组
- 如何在选项卡视图Qt中设置一个新项目,并保存以前的项目
- 预处理器:插入结构名称中的前一个行号
- 我在c++代码中生成了一个运行时#3异常
- 我想将一个对T类型的非常量左值引用绑定到一个T类型的临时值
- 从链接列表c++中删除一个项目
- 告诉一个 const char 数组,除了编译时 C 样式的字符串外,它不以 '