在gcc 4.7.2上,我可以写:
int x = 1, y = 2;
std::min<int&>(x,y) = 3;
编译并最终将{3}分配给x
。但是我可以为std::min
找到的每个引用都说它返回const T&
。这个行为标准是否也是如此?
答案 0 :(得分:4)
C ++ 11中有四个std::min
函数模板[algorithms.general] / 2:
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);
(我不确定是否为了获取指针,可能会有额外的重载。)
为简化表示法,我将在此处使用"continental const placement":
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
。这可能需要std::min
的重载实例化,其需要initializer_list
,但它不需要initializer_list<int&>
的实例化。与调用表达式(例如std::min<int&>(x,y)
)中的隐式重载决策进行比较时,请注意我们不需要在此处将某种类型转换为intializer_list<int&>
:我们只需要检查initializer_list<int&>
(函数的第一个参数的类型)与int&
(ft
的第一个参数的类型)的类型相同。
因此,我得出结论,上面的代码形式良好且表现良好。这意味着libc ++中存在一个错误。对于那些感兴趣的人,libc++ dispatches第一个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
实例化第一个std::min<int&>
重载并替换int&
时,会发生reference collapsing:
在最初的T const&
参数类型中,T
代替int&
。这会产生int& const&
。 const&
已合并int&
到int&
类型。实例化函数的签名变为:
int& min<int&>(int& a, int& b);
因此,根据f
的上述定义,我们可以写:
f(x, y) = 42;
如果函数的返回类型是左值引用,则函数调用表达式是左值。值类别与const-ness无关。 std::min
在任何情况下都会返回左值。