[英]decltype() on std::move() of a value capture in a lambda results in an incorrect type
似乎 Clang (9.0.0) 或我对decltype()
如何在标准中指定工作的理解有问题。 参考以下代码,
#include <utility>
#include <string>
template <typename...> class WhichType;
template <typename T>
std::remove_reference_t<T>&& move_v2(T&& t) {
WhichType<std::remove_reference_t<T>&&>{};
return static_cast<std::remove_reference_t<T>&&>(t);
}
int main() {
auto x = std::string{"a"};
[v = x]() {
// move_v2(v);
// WhichType<decltype(move_v2(v))>{};
WhichType<decltype(std::move(v))>{};
}();
}
上面的代码有编译器输出implicit instantiation of undefined template 'WhichType<std::__1::basic_string<char> &&>'
而不是期望的const std::__1::basic_string<char> &&
在WhichType
的模板参数中. 在move_v2
本身中使用move_v2
或WhichType
似乎输出正确的东西。
但是,Clang 似乎也对std::move(v)
表达式进行了重载解析,正如我所期望的https://wandbox.org/permlink/Nv7yXnCbqxjJMVvX 。 这让我的一些担忧消失了,但我仍然不理解 lambda 中decltype()
的行为。
在这种特殊情况下,GCC 似乎没有这种不一致https://wandbox.org/permlink/5mhrOzLn5XZO8LNB 。
如果我对decltype()
理解有误,有人可以纠正我,或者指出此错误在 clang 中出现的确切位置吗? 乍一看似乎有点吓人。 在 SFINAE 或类似的东西中使用时,这可能会导致问题。
我已经做了一些挖掘,在我看来,答案比某些评论看起来更微妙。
首先是对先前问题的回答。 引用重要部分:
[C++11: 5.1.2/14]:
如果实体被隐式捕获并且默认捕获为=
或者使用不包含&
的捕获显式捕获实体,则该实体通过复制捕获。 对于副本捕获的每个实体,在闭包类型中声明了一个未命名的非静态数据成员。 这些成员的声明顺序未指定。 如果实体不是对对象的引用,则此类数据成员的类型是对应的捕获实体的类型,否则为引用类型。 [..]
然后是另一个问题的答案。 再次引用:
5 非泛型 lambda 表达式的闭包类型具有公共内联函数调用运算符 [...]此函数调用运算符或运算符模板声明为
const
(9.3.1) 当且仅当 lambda 表达式的参数声明-clause后面没有mutable
。
然后我把它放在一个小测试中:
#include <iostream>
using std::cout;
using std::endl;
void foo(const std::string&) {
cout << "void foo(const std::string&)" << endl;
}
void foo(std::string&) {
cout << "void (std::string&)" << endl;
}
struct klaf
{
std::string b;
void bla() const
{
cout << std::boolalpha << std::is_const<decltype(b)>::value << endl;
foo(b);
}
};
int main()
{
klaf k;
k.bla();
std::string s;
const std::string s2;
auto lam = [=]() {
cout << std::boolalpha << std::is_const<decltype(s)>::value << endl;
foo(s);
cout << std::boolalpha << std::is_const<decltype(s2)>::value << endl;
foo(s2);
};
lam();
}
哪些输出(以及 GCC、Clang 和 MSVC 中的输出相同):
false
void foo(const std::string&)
false
void foo(const std::string&)
true
void foo(const std::string&)
klaf::b
(显然)不是const
,但由于klaf::bla
函数是const
,因此klaf::b
在对foo
的调用中被视为const
。
在lam
中也是如此,其中s
由类型为std::string
值捕获。 然而, s2
已被声明为const std::string
并且它延续到 lambda 中数据成员的类型。
简而言之:在 lambda 中按值捕获不会使捕获的成员本身成为const
,但由于 lambda 的operator()
是const
,因此成员在该函数中被提升为const
(除非 lambda 被声明为可变的)。
编辑:
受到@arnes 评论的启发,我发现 GCC 和 Clang 有所不同:
int main()
{
int i = 12;
auto lam = [=, ic = i]() {
cout << std::boolalpha << std::is_const<decltype(i)>::value << endl;
cout << std::boolalpha << std::is_const<decltype(ic)>::value << endl;
};
lam();
}
这会产生false false
的铛,但false true
在GCC。 换句话说,带有初始值设定项的捕获在 GCC 中变为const
而在 Clang 中则不然。
叮当错了。 decltype(std::move(v))
应该是const &&
因为CV-预选赛v
(这相当于this->v
)是的CV-预选赛工会*this
(这是const
在operator()
不可mutable
lambda) 和v
(没有),所以v
是一个const
左值。 然后, std::move
转换为相应类型的 xvalue,因此decltype
应该是const &&
。
当应用于 id 表达式或成员访问表达式 ( this->v
) 时, decltype
行为特别,但这里的情况并非如此。 std::move(v)
是一个复杂的表达式,因此它被视为普通表达式。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.