简体   繁体   English

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

[英]decltype() on std::move() of a value capture in a lambda results in an incorrect type

It seems like there either is something wrong with Clang (9.0.0) or my understanding of how decltype() is specified to work in the standard.似乎 Clang (9.0.0) 或我对decltype()如何在标准中指定工作的理解有问题。 With reference to the following code,参考以下代码,

#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))>{};
    }();
}

The code above has the compiler output implicit instantiation of undefined template 'WhichType<std::__1::basic_string<char> &&>' instead of the expected const std::__1::basic_string<char> && in the template parameters of WhichType .上面的代码有编译器输出implicit instantiation of undefined template 'WhichType<std::__1::basic_string<char> &&>'而不是期望的const std::__1::basic_string<char> &&WhichType的模板参数中. Using move_v2 or WhichType in move_v2 itself seems to output the correct thing though.move_v2本身中使用move_v2WhichType似乎输出正确的东西。

However, Clang also seems to do overload resolution on the std::move(v) expression as I expected https://wandbox.org/permlink/Nv7yXnCbqxjJMVvX .但是,Clang 似乎也对std::move(v)表达式进行了重载解析,正如我所期望的https://wandbox.org/permlink/Nv7yXnCbqxjJMVvX This makes some of my worries go away, but I still don't understand the behavior of decltype() inside the lambda.这让我的一些担忧消失了,但我仍然不理解 lambda 中decltype()的行为。

GCC does not seem to have this inconsistency in this particular case https://wandbox.org/permlink/5mhrOzLn5XZO8LNB .在这种特殊情况下,GCC 似乎没有这种不一致https://wandbox.org/permlink/5mhrOzLn5XZO8LNB


Could someone correct me if I'm wrong with my understading of decltype() or point me to the exact places where this bug manifests in clang?如果我对decltype()理解有误,有人可以纠正我,或者指出此错误在 clang 中出现的确切位置吗? Seems a bit scary from first glance.乍一看似乎有点吓人。 This can cause problems when used in SFINAE or something similar.在 SFINAE 或类似的东西中使用时,这可能会导致问题。

I have done a bit of digging, and it seems to me that the answer is a bit more nuanced than some of the comments makes it seem.我已经做了一些挖掘,在我看来,答案比某些评论看起来更微妙。

First there is this answer to an earlier question .首先是对先前问题的回答 To quote the important part:引用重要部分:

[C++11: 5.1.2/14]: An entity is captured by copy if it is implicitly captured and the capture-default is = or if it is explicitly captured with a capture that does not include an & . [C++11: 5.1.2/14]:如果实体被隐式捕获并且默认捕获=或者使用不包含&的捕获显式捕获实体,则该实体通过复制捕获 For each entity captured by copy, an unnamed non-static data member is declared in the closure type.对于副本捕获的每个实体,在闭包类型中声明了一个未命名的非静态数据成员。 The declaration order of these members is unspecified.这些成员的声明顺序未指定。 The type of such a data member is the type of the corresponding captured entity if the entity is not a reference to an object, or the referenced type otherwise.如果实体不是对对象的引用,则此类数据成员的类型是对应的捕获实体的类型,否则为引用类型。 [..] [..]

Then there is this answer to another question .然后是另一个问题的答案 Again quote:再次引用:

5 The closure type for a non-generic lambda-expression has a public inline function call operator [...] This function call operator or operator template is declared const (9.3.1) if and only if the lambda-expression's parameter-declaration-clause is not followed by mutable . 5 非泛型 lambda 表达式的闭包类型具有公共内联函数调用运算符 [...]此函数调用运算符或运算符模板声明为const (9.3.1) 当且仅当 lambda 表达式的参数声明-clause后面没有mutable

I then put this together in a small test:然后我把它放在一个小测试中:

#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();
}

Which outpouts (and the outputs are the same in GCC, Clang, and MSVC):哪些输出(以及 GCC、Clang 和 MSVC 中的输出相同):

false
void foo(const std::string&)
false
void foo(const std::string&)
true
void foo(const std::string&)

klaf::b is (obviously) not const , but since the klaf::bla function is const , then klaf::b is treated as const in the call to foo . klaf::b (显然)不是const ,但由于klaf::bla函数是const ,因此klaf::b在对foo的调用中被视为const

The same is true in lam where s is captured by value with a type of std::string .lam中也是如此,其中s由类型为std::string值捕获。 However, s2 has been declared as const std::string and that carries over to the type of the data member in the lambda.然而, s2已被声明为const std::string并且它延续到 lambda 中数据成员的类型。

In short: Capture by value in a lambda does not make the captured member itself const , but since operator() for the lambda is const , then the members are promoted to const in that function (unless the lambda is declared mutable).简而言之:在 lambda 中按值捕获不会使捕获的成员本身成为const ,但由于 lambda 的operator()const ,因此成员在该函数中被提升为const (除非 lambda 被声明为可变的)。

EDIT:编辑:

Inspired by comment from @arnes I found a difference is GCC and Clang:受到@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();
}

This produces false false in Clang, but false true in GCC.这会产生false false的铛,但false true在GCC。 In other words, a capture with an initializer becomes const in GCC but not in Clang.换句话说,带有初始值设定项的捕获在 GCC 中变为const而在 Clang 中则不然。

Clang is wrong.叮当错了。 decltype(std::move(v)) should be const && because the cv-qualifier v (which is equivalent to this->v ) is the union of the cv-qualifiers of *this (which is const in the operator() of a non- mutable lambda) and v (which is none), so v is a const lvalue. decltype(std::move(v))应该是const &&因为CV-预选赛v (这相当于this->v )是的CV-预选赛工会*this (这是constoperator()不可mutable lambda) 和v (没有),所以v是一个const左值。 Then, std::move converts to an xvalue of the corresponding type, so decltype should be const && .然后, std::move转换为相应类型的 xvalue,因此decltype应该是const &&

decltype behaves specially when applied to an id-expression or a member access expression ( this->v ), but that's not the case here.当应用于 id 表达式或成员访问表达式 ( this->v ) 时, decltype行为特别,但这里的情况并非如此。 std::move(v) is a complicated expression, so it is treated as an ordinary expression. std::move(v)是一个复杂的表达式,因此它被视为普通表达式。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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