C ++临时应该是不变的吗?

时间:2015-12-27 08:17:28

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

我有一个C ++类,它具有以下接口:

class F {
public:
    F(int n, int d);
    // no other constructors/assignment constructors defined
    F& operator *= (const F&);
    F& operator *= (int);
    int n() const;
    int d() const;
};

我有以下代码:

const F a{3, 7};
const F b{5, 10};
auto result = F{a} *= b; // How does this compile?

在Visual Studio(VS)2013下,注释行编译时没有错误。在VS2015下,产生错误C2678:

error C2678: binary '*=': no operator found 
    which takes a left-hand operand of type 'const F' 
    (or there is no acceptable conversion)
note: could be 'F &F::operator *=(const F &)'
note: or       'F &F::operator *=(int)'
note: while trying to match the argument list '(const F, const F)'

我的期望是F{a}会创建a的非常量临时副本,operator *= (b)将被应用到result之后,临时对象将被分配给auto result = F(a) *= b; }。我没想到临时是一个常数。有趣的是:/****************************************** Subway navigation (by sOltan stackoverflow.com) *******************************************/ .subway-station { color: rgb(255, 255, 255); height: 100%; width: 100px; position: relative; } .subway-open { top: 60px; /* start with a margin from the top of the table cell */ height: 100%; /* draw through the circled number */ left: 40px; /* indent this much */ width: 10px; position: absolute; /* position relative to ancestor subway-station */ z-index: 10; color: rgb(68, 68, 68); background: rgb(205, 205, 205) none repeat scroll 0% 0% / auto padding-box border-box; } .subway-close { top: 0px; /* start from the top of the table cell */ height: 50px; /* draw line this much from top */ left: 40px; /* indent this much */ width: 10px; /* width of the line */ position: absolute; /* position relative to ancestor subway-station */ z-index: 10; color: rgb(68, 68, 68); background: rgb(205, 205, 205) none repeat scroll 0% 0% / auto padding-box border-box; } .subway-line { bottom: 0px; /* this indicates to start drawing line from the bottom */ height: 100%; /* draw all the way to the top of the table cell */ left: 40px; /* indent this much */ width: 10px; position:absolute; /* position relative to ancestor subway-station */ z-index: 10; color: rgb(68, 68, 68); background: rgb(205, 205, 205) none repeat scroll 0% 0% / auto padding-box border-box; } .number { font-family: 'Lato'; z-index: 20; position: absolute; left: 0px; } .circled { display:inline-block; margin: 10px; border-radius: 50%; padding: 10px 20px; background: #fff; border: 8px solid #CDCDCD; color: #CDCDCD; text-align: center; font: 32px Arial, sans-serif; z-index: 20; position: absolute; } .circled:hover { background-color: #586CEF !important; color: white; } 在VS2015中编译没有错误,我认为它应该在语义上相同。

我的问题是:哪种行为是正确的VS2015或VS2013&为什么?

非常感谢

4 个答案:

答案 0 :(得分:8)

Visual Studio 2015没有为以下内容生成正确的结果:

F{a}

结果应该是一个prvalue( gcc和clang都有这个结果),但它产生一个左值。我使用OP的代码的以下修改版本来产生这个结果:

#include <iostream>

class F {
public:
    F(int n, int d) :n_(n), d_(d) {};
    F(const F&) = default ;
    F& operator *= (const F&){return *this; }
    F& operator *= (int) { return *this; }
    int n() const { return n_ ; }
    int d() const { return d_ ; }
    int n_, d_ ;
};

template<typename T>
struct value_category {
    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";
};

#define VALUE_CATEGORY(expr) value_category<decltype((expr))>::value

int main()
{
  const F a{3, 7};
  const F b{5, 10};   
  std::cout << "\n" <<  VALUE_CATEGORY( F{a} ) <<  "\n";
}

帽子提示Luc Danton for the VALUE_CATEGORY() code

使用具有相对较新版本的webcompiler的Visual Studio产生:

lvalue

在这种情况下必须是 const 才能产生我们看到的错误。虽然gcc和clang( see it live )都会产生:

prvalue

这可能与同样令人困惑的Visual Studio错误std::move of string literal - which compiler is correct?有关。

注意我们可以使用 const F 来解决gcc和clang的相同问题:

using cF = const F ;
auto result = cF{a} *= b; 

因此,Visual Studio不仅为我们提供了错误的值类别,而且还随意添加了一个cv-qualifier。

汉斯在他对你的问题的评论中指出,使用F(a)会产生预期的结果,因为它正确地产生了一个prvalue。

C ++标准草案的相关部分是5.2.3 [expr.type.conv] 部分,其中说:

  

类似地,一个simple-type-specifier或typename-specifier后跟一个braced-init-list创建一个临时的   具有指定的braced-init-list的指定类型direct-list-initialized(8.5.4)的对象,其值为   该临时对象作为prvalue。

请注意,据我所知,这不是"old MSVC lvalue cast bug"。该问题的解决方案是使用/Zc:rvalueCast来解决此问题。这个问题在不正确添加cv-qualifier方面也有所不同,据我所知,这与上一期不会发生。

答案 1 :(得分:0)

我的想法是VS2015中的一个错误,因为如果您指定用户定义的复制构造函数:

F(const F&);

或使变量a非常规代码将成功编译。

a中的对象常量转换为新创建的对象。

答案 2 :(得分:0)

Visual C ++在一段时间内遇到了一个错误,即身份转换不会产生临时变量,但会引用原始变量。

此处有错误报告:identity cast to non-reference type violates standard

答案 3 :(得分:0)

来自http://en.cppreference.com/w/cpp/language/copy_elision

  

在下列情况下,允许编译器省略   即使复制/移动构造函数,也可以复制和移动类对象的构造函数   析构函数有可观察到的副作用。

     

.......

     

无名临时未绑定到任何引用时,将被移动或   复制到相同类型的对象中(忽略顶级cv -   资格),省略了复制/移动。那个临时的时候   在构造中,它直接在存储器中构造   否则被移动或复制到。当无名的临时是   返回语句的参数,这种复制省略的变体被称为   RVO,“返回值优化”。

因此编译器可以选择忽略副本(在这种情况下,它将作为非const类型的隐式转换)。