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

Cur*_*ous 6 c++ clang decltype language-lawyer c++17

似乎 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))>{};
    }();
}
Run Code Online (Sandbox Code Playgroud)

上面的代码有编译器输出implicit instantiation of undefined template 'WhichType<std::__1::basic_string<char> &&>',而不是预期const std::__1::basic_string<char> &&在模板参数WhichType。使用move_v2WhichTypemove_v2本身似乎输出正确的事情,但。

但是,Clang 似乎也std::move(v)按照我的预期对表达式进行了重载解析https://wandbox.org/permlink/Nv7yXnCbqxjJMVvX。这让我的一些担忧消失了,但我仍然不了解decltype()lambda 内部的行为。

在这种特殊情况下,GCC 似乎没有这种不一致https://wandbox.org/permlink/5mhrOzLn5XZO8LNB


如果我对我的理解有误decltype()或指出此错误在 clang 中出现的确切位置,有人可以纠正我吗?乍一看似乎有点吓人。在 SFINAE 或类似的东西中使用时,这可能会导致问题。

Fro*_*yne 0

我做了一些挖掘,在我看来,答案比一些评论看起来更微妙。

\n\n

首先是对之前问题的回答。引用重要部分:

\n\n
\n

[C++11: 5.1.2/14]: 如果实体是隐式捕获的并且捕获默认值=,或者如果使用不包含&. 对于复制捕获的每个实体,在闭包类型中声明一个未命名的非静态数据成员。这些成员的声明顺序未指定。如果实体不是对对象的引用,则此类数据成员的类型是相应捕获实体的类型,否则是引用的类型。 [..]

\n
\n\n

然后是另一个问题的这个答案。再次引用:

\n\n
\n

5 非泛型 lambda 表达式的闭包类型具有公共\n 内联函数调用运算符 [...]当且仅当\n lambda 时,声明此函数调用运算符或\n 运算符模板const(9.3.1) -表达式\xe2\x80\x99s 参数声明子句后面没有\n mutable

\n
\n\n

然后我将其放在一个小测试中:

\n\n
#include <iostream>\nusing std::cout;\nusing std::endl;\n\nvoid foo(const std::string&) {\n    cout << "void foo(const std::string&)" << endl;\n}\n\nvoid foo(std::string&) {\n    cout << "void (std::string&)" << endl;\n}\n\nstruct klaf\n{\n    std::string b;\n\n    void bla() const\n    {\n        cout << std::boolalpha << std::is_const<decltype(b)>::value << endl;\n        foo(b);\n    }\n};\n\nint main()\n{\n    klaf k;\n    k.bla();\n\n    std::string s;\n    const std::string s2;\n    auto lam = [=]() {\n        cout << std::boolalpha << std::is_const<decltype(s)>::value << endl;\n        foo(s);\n\n        cout << std::boolalpha << std::is_const<decltype(s2)>::value << endl;\n        foo(s2);\n    };\n    lam();\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

哪些输出(GCC、Clang 和 MSVC 中的输出相同):

\n\n
false\nvoid foo(const std::string&)\nfalse\nvoid foo(const std::string&)\ntrue\nvoid foo(const std::string&)\n
Run Code Online (Sandbox Code Playgroud)\n\n

klaf::b是(显然)不是const,但由于该klaf::bla函数是const,所以 thenklaf::b被视为const在对 的调用中foo

\n\n

lam在wheres被类型为 的值捕获时也是如此std::string。然而,s2已被声明为const std::stringand 并延续到 lambda 中数据成员的类型。

\n\n

简而言之:在 lambda 中按值捕获不会使捕获的成员本身成为const,但由于operator()lambda 是const,因此成员将在该函数中提升到const(除非 lambda 被声明为可变)。

\n\n

编辑:

\n\n

受到 @arnes 评论的启发,我发现 GCC 和 Clang 之间存在差异:

\n\n
int main()\n{\n    int i = 12;\n    auto lam = [=, ic = i]() {\n        cout << std::boolalpha << std::is_const<decltype(i)>::value << endl;\n        cout << std::boolalpha << std::is_const<decltype(ic)>::value << endl;\n    };\n    lam();\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

这在 Clang 中生成false false,但false true在 GCC 中生成。换句话说,带有初始值设定项的捕获const在 GCC 中变为,但在 Clang 中则不然。

\n