是 std::min 返回一个 lval 标准



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

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

最新更新