过年之无用知识研究:std::is_assignable中的declval<_Dest>() = declval<_Src>()
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 int
class _Other2 = _Ty2, //_Ty2:int , _Other2: int
enable_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 compile
lvalue<double>() = rvalue<int>(); // OK
lvalue<double>() = lvalue<int>(); // OK
return 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;
};
Understanding how declval is working in the copy_assignment situation
I was watching Dr. Walter E. Brown's Template Meta-programming talk. In his presentation he presents code like so for is_copy_assignable
:
template<class U, class = decltype(declval<U&>() = declval<U const&>())>
static true_type try_assignment(U &&);
What I am having trouble with is how is the assignment operator being called in this instance. When I try and reason about this code and say substitute a dummy type, say int
, in place of the U
I get:
template<class U, class = decltype(declval<int&>() = declval<int const&>())>
template<class U, class = decltype(int& = int const&)>
So I am wondering then how can this help us determine if the assignment operator is valid. If I understand declval
correctly this code will not even evaluate so then how can one determine from int& = int const&
, which doesn't even evaluate, if there is or isn't a assignment operator defined for U
.
I understand that in most cases the copy-assignment operator would be defined as
C& operator=(const C& other)
which looks a lot like what is above, but still since nothing is being evaluated then what use is this information.
- c++
- c++11
Share
Improve this question
Follow
edited Feb 28, 2018 at 1:02
Lightness Races in Orbit
385k7777 gold badges664664 silver badges1.1k1.1k bronze badges
asked Feb 5, 2018 at 2:21
cogle
1,06911 gold badge1414 silver badges2626 bronze badges
- Why did you turn
declval<int&>()
intoint&
? What do you thinkdeclval<int&>()
does?– Mooing Duck
Commented Feb 28, 2018 at 1:06
Add a comment
1 Answer
Sorted by:
1
I don't really follow what you intended in performing the following step:
template<class U, class = decltype(declval<int&>() = declval<int const&>())>
template<class U, class = decltype(int& = int const&)>
declval
says: pretend to return a value of type of the template argument. I say pretend because the function isn't really defined, only declared, so it can only be used in an unevaluated context. Inside of decltype
is an unevaluated contexts because you're only checking types.
So, the expression decltype(declval<int&>() = declval<int const&>())
is basically saying: if I had an int&
, call it x
, and I had a int const&
, call it y
, what is the type of the expression x = y
?
As you can probably guess this calls the assignment operator and this expression will have a valid type (in this case, int&
). If you change int
to unique_ptr
, this expression won't have a valid type, because unique_ptr
is not assignable, causing type substitution to fail and eliminating this template from the overload or specialization set.
The reason to have declval at all is because it lets you create a value of any type; this is particularly useful for a) creating something of reference type, and b) creating non-reference types without assuming that they have e.g. a default constructor. Thus, use of declval
is extremely ubiquitous in high quality TMP.
Share
Improve this answer
Follow
answered Feb 5, 2018 at 2:47
-
2
Thanks, I was misunderstanding whatdeclval
actually was doing. Its pretending to return the value which of the type which is what I wasn't picking up on.– cogle
Commented Feb 5, 2018 at 3:14