?std::pair的默認operator=被delete掉了,取而代之的是兩個enable_if版本。
為什么這么設計,我的理解是在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;
};