繁体   English   中英

lambda 中值捕获的 std::move() 上的 decltype() 导致类型不正确

[英]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_v2WhichType似乎输出正确的东西。

但是,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 (这是constoperator()不可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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM