std :: move of string literal - 哪个编译器正确?

时间:2015-12-08 16:09:48

标签: c++ visual-studio-2015 language-lawyer

给出以下代码:

#include <string>
void foo()
{
  std::string s(std::move(""));
}

这与Apple clang(xcode 7)编译,并且不与visual studio 2015一起生成以下错误:

error C2440: 'return': cannot convert from 'const char [1]' to 'const char (&&)[1]'
note: You cannot bind an lvalue to an rvalue reference
main.cpp(4): note: see reference to function template instantiation 'const char (&&std::move<const char(&)[1]>(_Ty) noexcept)[1]' being compiled
    with
    [
        _Ty=const char (&)[1]
    ]

暂时忽略移动是多余的,在这种情况下哪个标准库实现更正确?

我的感觉是""的类型为const char[1],因此std::move应返回std::remove_reference<const char[1]&>::type&& const char[1]&&

在我看来,这应该衰减到const char*

或者我是否误解了规则?

2 个答案:

答案 0 :(得分:11)

这看起来像一个Visual Studio错误。这归结为std::move,如果我们查看cppreference页面,它会有以下签名:

template< class T >
typename std::remove_reference<T>::type&& move( T&& t );

然后它返回:

static_cast<typename std::remove_reference<T>::type&&>(t) 

匹配草案C ++标准部分20.2.4转发/移动帮助者[转发]。

使用code I grabbed from here我们可以看到以下示例:

#include <iostream>

template<typename T>
struct value_category {
    // Or can be an integral or enum value
    static constexpr auto value = "prvalue";
};

template<typename T>
struct value_category<T&> {
    static constexpr auto value = "lvalue";
};

template<typename T>
struct value_category<T&&> {
    static constexpr auto value = "xvalue";
};

// Double parens for ensuring we inspect an expression,
// not an entity
#define VALUE_CATEGORY(expr) value_category<decltype((expr))>::value


int main()
{   
    std::cout << VALUE_CATEGORY( static_cast<std::remove_reference<const char[1]>::type&&>("") ) << std::endl ;

}

使用Wandbox从gcc和clang生成以下答案:

xvalue

以及使用webcompiler的Visual Studio中的答案:

lvalue

因此Visual Studio对原始代码的错误:

  

您不能将左值绑定到右值参考

当它尝试将static_cast<typename std::remove_reference<T>::type&&>(t)的结果绑定到std::remove_reference<T>::type&&时,std::move的返回值。

我没有看到任何理由static_cast应该像在Visual Studio案例中那样生成左值。

答案 1 :(得分:4)

让我们首先将其分为两部分,以便我们分别对每一部分进行分析:

#include <string>
void foo()
{
    auto x = std::move("");
    std::string s(x);
}

第二部分,从x(无论可能是什么类型)初始化字符串,这不是真正的问题或问题。手头的问题(至少在我看来)是第一行,我们尝试将rvalue引用绑定到字符串文字。

标准的相关部分是[dcl.init.ref]/5(§8.5.3/ 5,至少在我见过的C ++标准的大多数版本中)。

首先是:

  

对类型'' cv1 T1''的引用由类型为'' cv2 T2''的表达式初始化,如下所示。

接下来是一个子弹列表。第一项仅涵盖左值引用,因此我们将忽略它。第二项说:

  

如果初始化表达式为
   - 是一个xvalue(但不是位字段)类prvalue,数组prvalue或函数lvalue [...]
   - 具有班级类型(即T2是班级类型)[...]
  - 除此以外      - 如果T1T2是类类型且T1不是,则不是与T2相关的引用<...]

显然,这些都不适用。字符串文字不是xvalue,类prvalue,数组prvalue或函数lvalue,也不是类类型。

仅留下:

  

如果T1T2的参考相关:
   - cv1 应与 cv2 相同的cv资格,或更高的cv资格,以及
   - 如果引用是右值引用,则初始化表达式不应为左值。

由于在这种情况下,编译器推导出转换结果的类型,因此与初始化程序的类型相关联。在这种情况下,正如我强调的部分所说,初始化表达式不能是左值。

这只留下字符串文字是否是左值的问题。至少随便,我不能立即找到C ++标准的部分,说它们是(在字符串文字部分没有提到它)。如果它不存在,下一步将是查看基本文档(C标准),该文档明确指出字符串文字是左值(N1570,§6.5.1/ 4)

  

字符串文字是主要表达式。它是一个左值类型,详见6.4.5。

我希望我能在C ++标准中找到直接的声明(我很确定它应该存在),但无论出于何种原因我现在都找不到它。