std::pair的默认operator=被delete掉了,取而代之的是两个enable_if版本。
为什么这么设计,我的理解是pair作为左值时,里面的first如果是const,那么就不允许了。比如,在std::map里,已经保存的元素的key值是不能被修改的,这一点会在编译时报出错误。比如
注意,下面的代码会修改key值,编译时出现错误:
两个enable_if版本推导失败,最后落到了:pair& operator=(const volatile pair&) = delete;
这个版本,但这个版本又被删除了。所以出现了编译错误。{std::map<int, int> m;//初始化auto it = m.begin();*it = std::make_pair(1, 1); //问题就出在这里
}就会提示:
error C2679: 二进制“=”: 没有找到接受“std::pair<int,int>”类型的右操作数的运算符(或没有可接受的转换)
1>d:\devtools\vs2017\vc\tools\msvc\14.16.27023\include\utility(276): note: 可能是“std::pair<const _Kty,_Ty> &std::pair<const _Kty,_Ty>::operator =(volatile const std::pair<const _Kty,_Ty> &)”
1> with
1> [
1> _Kty=int,
1> _Ty=int
1> ]
1>c:\work\hchx\ceripipe\codestudy\consoleapplication2\consoleapplication1\consoleapplication1.cpp(2114): note: 尝试匹配参数列表“(std::pair<const _Kty,_Ty>, std::pair<int,int>)”时
1> with
1> [
1> _Kty=int,
1> _Ty=int
1> ]
代码:
std::pair<int, int> data0 = std::make_pair(1, 2);
std::pair<const int, int> data1 = std::make_pair(3, 4);
data0 = data1;堆栈中可以看到推导结果:
1.exe!std::pair<int,int>::operator=<int const ,int,0>
(const std::pair<int const ,int> & _Right={...}) 行 288 C++推导出的符号,
_Ty1和_Other1类型是:const int
_Ty2和_Other2类型是:int查看pair的operator=的enable_if不分,
is_assignable<_Ty1&, const _Other1&>怎么算,替换下来就是:
is_assignable<const int&, const const int&>
看到const const int&,居然有两个const,能行吗?考虑到is_asignable的实现,也就是相当于,
{using T = decltype(declval<int&>() = declval<const const int&>());int b = 0;T a = b;std::ignore = a;
}
vs2015下编译成功。神奇。
而is_assignable<_Ty1&, const _Other1&>就是:is_assignable<int&, const int&>
{using T = decltype(declval<int&>() = declval<const int&>());int b = 0;T a = b;std::ignore = a;
}
vs2015下也能编译成功。std::cout << boost::typeindex::type_id_with_cvr<const const int&>().pretty_name() << std::endl;
打印结果是:int const & __ptr64所以,enable_if_t<conjunction_v<is_assignable<_Ty1&, const _Other1&>,is_assignable<_Ty2&, const _Other2&>>, int>推导成功,
相当于:enable_if_t<conjunction_v<true,true>, int>
-> enable_if_t<true, int>
-> int所以才走到了下面的operator=的版本。结合源码:pair& operator=(const volatile pair&) = delete; //默认的版本已被删除template<class _Other1 = _Ty1, //_Ty1:const int, _Other1: const intclass _Other2 = _Ty2, //_Ty2:int , _Other2: intenable_if_t<conjunction_v<is_assignable<const int&, const const int&>,is_assignable<int& , const int&>>, int> = 0>pair& operator=(const pair<const int, int>& _Right)_NOEXCEPT_COND(is_nothrow_assignable_v<_Ty1&, const _Other1&>&& is_nothrow_assignable_v<_Ty2&, const _Other2&>) // strengthened{first = _Right.first;second = _Right.second;return (*this);}
How to interpret declval<_Dest>() = declval<_Src>() in is_assignable
I am trying to figure out how to interpret declval<_Dest>() = declval<_Src>() in the implementation of is_assignable.
declval turns a type into a reference. Given that, I translate the expression into one of the following four possibilities:
- _Dest&& = _Src&&
- _Dest&& = _Src&
- _Dest& = _Src&&
- _Dest& = _Src&
I then created two helper functions.
template <typename T> T rvalue();
template <typename T> T& lvalue();
My understanding is the four expressions can be realized by using the template functions.
- _Dest&& = _Src&& -----> rvalue<_Dest>() = rvalue<_Src>()
Same goes for the other three.
Then I simulated decltype(declval<_Dest>() = declval<_Src>(), ..) by compiling the templated function version of each of the possibilities for three pairs of concrete types.
- _Dest=int, _Src=int. Compiler accepts #3 and #4. is_assignable returned true for #3 and #4. They agreed.
- _Dest=int, _Src=double. Same result as
- _Dest=double, _Src=int. For this one, the compiler and is_assignable didn't agree. Compiler again does not like assigning to rvalues. However, is_assignable returns true for all four possibilities.
My questions are
- Did I interpret declval<_Dest>() = declval<_Src>() correctly? In order words, does this really translate into the four possibilities. If yes, can each one be mapped to a templated function expression?
- Why the compiler and is_assignable disagree on the _Dest=double, _Src=int case?
Thanks.
- c++
- c++11
Share
Improve this question
Follow
edited Dec 6, 2013 at 5:34
ildjarn
63k99 gold badges131131 silver badges216216 bronze badges
asked Dec 6, 2013 at 2:35
Candy Chiu
6,67999 gold badges5151 silver badges7070 bronze badges
-
1
Which compiler is "the" compiler? At least one compiler appears to work precisely as you expect.– Igor Tandetnik
Commented Dec 6, 2013 at 3:18 - The compiler is VC++2013
– Candy Chiu
Commented Dec 6, 2013 at 13:21
https://ideone.com/QdRjZB#include <iostream>
#include <type_traits>
using namespace std;template <typename T>
T rvalue() { return T(); }template <typename T>
T& lvalue() { static T t; return t; }int main() {cout << is_assignable<double, int>::value;cout << is_assignable<double, int&>::value;cout << is_assignable<double&, int>::value;cout << is_assignable<double&, int&>::value;// rvalue<double>() = rvalue<int>(); // Doesn't compile// rvalue<double>() = lvalue<int>(); // Doesn't compilelvalue<double>() = rvalue<int>(); // OKlvalue<double>() = lvalue<int>(); // OKreturn 0;
}
一个回答:
std::declval
is actually specified to be (C++11 §20.2.4 [declval] p1):
template <class T>
typename add_rvalue_reference<T>::type declval() noexcept;
The result of the reference collapsing rules (§8.3.2 [dcl.ref] p6) is that declval
returns an lvalue reference when T
is an lvalue reference type, and an rvalue reference otherwise. So yes, your interpretation is correct.
If your compiler thinks that double&&
is assignable from any type, then it has a bug. §5.17 [expr.ass] p1 states:
The assignment operator (
=
) and the compound assignment operators all group right-to-left. All require a modifiable lvalue as their left operand and return an lvalue referring to the left operand.
[emphasis mine].
Many programmers choose to emulate this behavior - assingment only to lvalues - with their own types by declaring the assignment operators with an lvalue reference qualifier:
class foo {foo& operator = (const foo&) & = default;foo& operator = (foo&&) & = default;
};