为什么右值引用参数与重载决策中的 const 引用匹配?
Why rvalue reference argument matches to const reference in overload resolution?
可能相关的文章:
- Overload resolution between object, rvalue reference, const reference
- std::begin and R-values
对于 STL 容器 C
、std::begin(C)
和包括 std::data(C)
在内的类似访问函数(C++17 起)应该具有与 C::begin()
相同的行为以及其他相应的 C
方法。但是,由于涉及 lvalue/rvalue 引用和常量的重载解析细节,我观察到了一些有趣的行为。
DataType1
是 int*
很容易预料的。此外,通过 Boost 的 type_id_with_cvr
确认了这一点。 const vector<int>
给出 int const*
不足为奇。
using T = vector<int>;
using DataType1 = decltype(T().data()); // int*
using CT = const T;
using DataType2 = decltype(CT().data()); // int const*
using boost::typeindex::type_id_with_cvr;
cout << type_id_with_cvr<DataType1>() << endl; // prints int*
...
我试过std::data
,它也可以处理数组和非STL容器。但它会产生 int const*
。类似地,std::begin
returns 一个 const 迭代器,即使 T
不是 const.
using T = vector<int>;
using DataType3 = decltype(std::data(T())); // int const* Why ???
using CT = const T;
using DataType4 = decltype(std::data(CT())); // int const*
问题:造成这种差异的原因是什么?我预计 C.data()
和 std::data(C)
会以相同的方式运行。
我的一些研究:为了为 DataType3
获得 int*
,T
必须显式转换为非常量左值引用类型。我尝试了 declval
,它正在工作。
using DataType3 = decltype(std::data(std::declval<T&>())); // int*
std::data
提供两个重载:
template <class _Cont> constexpr
auto data(_Cont& __c) -> decltype(__c.data()) { return __c.data(); }
// non-const rvalue reference argument matches to this version.
template <class _Cont> constexpr
auto data(const _Cont& __c) -> decltype(__c.data()) { return __c.data(); }
在解析 std::data
的重载函数时,非 const 右值引用 T()
匹配 const T&
版本而不是 T&
版本。
在标准(13.3,over.match)中找到这个特定的重载解析规则并不容易。如果有人能指出这个问题的确切规则,那就更清楚了。
此行为归因于重载解析规则。根据 标准 8.5.3/p5.2 引用 [dcl.init.ref],右值引用绑定到 const 左值引用。在这个例子中:
std::data(T())
您向 std::data
提供了一个右值。因此,由于重载决议规则,重载:
template <class _Cont> constexpr
auto data(const _Cont& __c) -> decltype(__c.data()) { return __c.data(); }
是一个更好的匹配。因此你得到 const int*
您不能将临时值绑定到非常量左值引用。
唯一有点令人惊讶的是 using DataType1 = decltype(T().data()); // int*
。
...不过还是正常的,member函数可以在临时对象上调用而不被当成const
。 这是成员函数和自由函数之间的另一个重要区别。
例如,在 C++98(前右值引用)中,不可能执行 std::ofstream("file.txt") << std::string("text")
,因为 operator<<
不是成员,临时对象被视为 const
.如果 operator<<
是 std::ofstream
的成员,那将是可能的(甚至可能有意义)。 (后来在 C++11 中使用右值引用情况发生了变化,但这一点仍然有效)。
这是一个例子:
#include<iostream>
struct A{
void f() const{std::cout << "const member" << std::endl;}
void f(){std::cout << "non const member" << std::endl;}
};
void f(A const&){std::cout << "const function" << std::endl;}
void f(A&){std::cout << "non const function" << std::endl;}
int main(){
A().f(); // prints "non const member"
f(A()); // prints "const function"
}
该行为是在临时构造和使用对象时暴露的。在我能想象的所有其他情况下,成员 f
等同于 f
自由函数。
(r-value
参考资格 &&
-- 对于成员和函数 -- 可以为您提供更细粒度的控制,但这不是问题的一部分。)
可能相关的文章:
- Overload resolution between object, rvalue reference, const reference
- std::begin and R-values
对于 STL 容器 C
、std::begin(C)
和包括 std::data(C)
在内的类似访问函数(C++17 起)应该具有与 C::begin()
相同的行为以及其他相应的 C
方法。但是,由于涉及 lvalue/rvalue 引用和常量的重载解析细节,我观察到了一些有趣的行为。
DataType1
是 int*
很容易预料的。此外,通过 Boost 的 type_id_with_cvr
确认了这一点。 const vector<int>
给出 int const*
不足为奇。
using T = vector<int>;
using DataType1 = decltype(T().data()); // int*
using CT = const T;
using DataType2 = decltype(CT().data()); // int const*
using boost::typeindex::type_id_with_cvr;
cout << type_id_with_cvr<DataType1>() << endl; // prints int*
...
我试过std::data
,它也可以处理数组和非STL容器。但它会产生 int const*
。类似地,std::begin
returns 一个 const 迭代器,即使 T
不是 const.
using T = vector<int>;
using DataType3 = decltype(std::data(T())); // int const* Why ???
using CT = const T;
using DataType4 = decltype(std::data(CT())); // int const*
问题:造成这种差异的原因是什么?我预计 C.data()
和 std::data(C)
会以相同的方式运行。
我的一些研究:为了为 DataType3
获得 int*
,T
必须显式转换为非常量左值引用类型。我尝试了 declval
,它正在工作。
using DataType3 = decltype(std::data(std::declval<T&>())); // int*
std::data
提供两个重载:
template <class _Cont> constexpr
auto data(_Cont& __c) -> decltype(__c.data()) { return __c.data(); }
// non-const rvalue reference argument matches to this version.
template <class _Cont> constexpr
auto data(const _Cont& __c) -> decltype(__c.data()) { return __c.data(); }
在解析 std::data
的重载函数时,非 const 右值引用 T()
匹配 const T&
版本而不是 T&
版本。
在标准(13.3,over.match)中找到这个特定的重载解析规则并不容易。如果有人能指出这个问题的确切规则,那就更清楚了。
此行为归因于重载解析规则。根据 标准 8.5.3/p5.2 引用 [dcl.init.ref],右值引用绑定到 const 左值引用。在这个例子中:
std::data(T())
您向 std::data
提供了一个右值。因此,由于重载决议规则,重载:
template <class _Cont> constexpr
auto data(const _Cont& __c) -> decltype(__c.data()) { return __c.data(); }
是一个更好的匹配。因此你得到 const int*
您不能将临时值绑定到非常量左值引用。
唯一有点令人惊讶的是 using DataType1 = decltype(T().data()); // int*
。
...不过还是正常的,member函数可以在临时对象上调用而不被当成const
。 这是成员函数和自由函数之间的另一个重要区别。
例如,在 C++98(前右值引用)中,不可能执行 std::ofstream("file.txt") << std::string("text")
,因为 operator<<
不是成员,临时对象被视为 const
.如果 operator<<
是 std::ofstream
的成员,那将是可能的(甚至可能有意义)。 (后来在 C++11 中使用右值引用情况发生了变化,但这一点仍然有效)。
这是一个例子:
#include<iostream>
struct A{
void f() const{std::cout << "const member" << std::endl;}
void f(){std::cout << "non const member" << std::endl;}
};
void f(A const&){std::cout << "const function" << std::endl;}
void f(A&){std::cout << "non const function" << std::endl;}
int main(){
A().f(); // prints "non const member"
f(A()); // prints "const function"
}
该行为是在临时构造和使用对象时暴露的。在我能想象的所有其他情况下,成员 f
等同于 f
自由函数。
(r-value
参考资格 &&
-- 对于成员和函数 -- 可以为您提供更细粒度的控制,但这不是问题的一部分。)