std :: min是否返回了lval标准?

时间:2014-10-17 01:47:33

标签: c++

在gcc 4.7.2上,我可以写:

int x = 1, y = 2;
std::min<int&>(x,y) = 3;

编译并最终将{3}分配给x。但是我可以为std::min找到的每个引用都说它返回const T&。这个行为标准是否也是如此?

1 个答案:

答案 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;

其中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。这可能需要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在任何情况下都会返回左值。