在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
在任何情况下都返回一个左值。