15

I have a C++ class that has the following interface:

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;
};

And I have the following code:

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

Under Visual Studio (VS) 2013, the commented line compiles without error. Under VS2015 , error C2678 is produced:

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)'

My expectation was that F{a} would create a non-const temporary copy of a to which operator *= (b) would be applied, after which the temporary object would be assigned to result. I did not expect the temporary to be a constant. Interestingly: auto result = F(a) *= b; compiles without error in VS2015, which I thought should be semantically the same.

My question is: which behaviour is correct VS2015 or VS2013 & why?

Many thanks

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
Mark
  • 181
  • 6
  • 1
    Looks like a bug at first sight, `F{a}` should be non-const – M.M Dec 27 '15 at 08:24
  • can you post a MCVE just to make sure there aren't any other factors? – M.M Dec 27 '15 at 08:25
  • 2
    Clearly it lost its marbles when it tried to infer a (const F, const F) initializer list. Just use `F(a)` instead. Use connect.microsoft.com to report bugs. – Hans Passant Dec 27 '15 at 09:14
  • @HansPassant yeah, this is puzzling bug, it seems like it may be related to [this other puzzling one](http://stackoverflow.com/q/34160614/1708801). – Shafik Yaghmour Dec 27 '15 at 11:57
  • First time this bug was noted on Stack Overflow (I believe): http://stackoverflow.com/questions/3986522 – Ben Voigt Dec 27 '15 at 16:42
  • Note Visual Studio has several similar issue as noted [here](http://stackoverflow.com/a/26508755/1708801) and [here](http://stackoverflow.com/q/16380966/1708801) and the solutions to those problems do not fix this one so they are not related to any of the old issues I know of. – Shafik Yaghmour Dec 27 '15 at 16:51
  • @M.M right it should not be constant unless the specifier itself is const, although there are more issues than that. – Shafik Yaghmour Dec 28 '15 at 00:53
  • 1
    Thank you all and especially to @shafikyaghmour. I am inclined to the view that this is indeed a VS bug. Ben Voigt reported something very similar 5 years ago against VS2010. MS closed it with a promise that it would be fixed in the next version. That was fulfilled until VS2015, so this ought to be a failed regression. More news shortly... – Mark Dec 28 '15 at 12:40
  • @MarvinLittlewood does that mean you are filing a bug report? If so it may be worth mentioning [this issue as well](http://stackoverflow.com/q/34160614/1708801) they may be related although it is not clear to me if they are. – Shafik Yaghmour Dec 29 '15 at 01:01
  • @ShafnikYaghmour thanks for the suggestion: I have added the issue to my report: [VisualStudio/feedback/details/2178733](https://connect.microsoft.com/VisualStudio/feedback/details/2178733/temporary-copy-of-const-type-incorrectly-results-in-an-lvalue) – Mark Dec 29 '15 at 11:11

4 Answers4

8

Visual Studio 2015 is not producing the correct result for:

F{a}

The result should be a prvalue(gcc and clang both have this result) but it is producing an lvalue. I am using the following modified version of the OP's code to produce this result:

#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";
}

Hat tip to Luc Danton for the VALUE_CATEGORY() code.

Visual Studio using webcompiler which has a relatively recent version produces:

lvalue

which must be const in this case to produce the error we are seeing. While both gcc and clang (see it live) produce:

prvalue

This may be related to equally puzzling Visual Studio bug std::move of string literal - which compiler is correct?.

Note we can get the same issue with gcc and clang using a const F:

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

so not only is Visual Studio giving us the wrong value category but it also arbitrarily adding a cv-qualifier.

As Hans noted in his comments to your question using F(a) produces the expected results since it correctly produces a prvalue.

The relevant section of the draft C++ standard is section 5.2.3 [expr.type.conv] which says:

Similarly, a simple-type-specifier or typename-specifier followed by a braced-init-list creates a temporary object of the specified type direct-list-initialized (8.5.4) with the specified braced-init-list, and its value is that temporary object as a prvalue.

Note, as far as I can tell this is not the "old MSVC lvalue cast bug". The solution to that issue is to use /Zc:rvalueCast which does not fix this issue. This issue also differs in the incorrect addition of a cv-qualifier which as far as I know does not happen with the previous issue.

Community
  • 1
  • 1
Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
  • oh, it's the old MSVC lvalue cast bug. Figured they might have not perpetuated that to list initialization... – M.M Dec 28 '15 at 04:00
  • @M.M how do you know? As far as I can tell the solution [as mentioned here](http://stackoverflow.com/a/26508755/1708801) is to use `/Zc:rvalueCast ` which does not fix this issue. – Shafik Yaghmour Dec 28 '15 at 04:04
  • @M.M it is definitely similar but the fact that the provided solution does not fix this issue and the result is a const lvalue which seems not to be the case other situations. So this seems related but distinct. – Shafik Yaghmour Dec 28 '15 at 09:43
0

My thoughts it's a bug in VS2015, because if you specify user defined copy constructor:

F(const F&);

or make variable a non-const code will be compiled successfully.

Looks like object's constness from a transferred into newly created object.

αλεχολυτ
  • 4,792
  • 1
  • 35
  • 71
0

Visual C++ has had a bug for some time where an identity cast doesn't produce a temporary, but refers to the original variable.

Bug report here: identity cast to non-reference type violates standard

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • I don't believe it is the same bug, as far as I understand that is that is the same issue as [this question](http://stackoverflow.com/a/26508755/1708801) and the solution is to use `/Zc:rvalueCast` which does not fix the problem here. – Shafik Yaghmour Dec 27 '15 at 16:49
0

From http://en.cppreference.com/w/cpp/language/copy_elision:

Under the following circumstances, the compilers are permitted to omit the copy- and move-constructors of class objects even if copy/move constructor and the destructor have observable side-effects.

.......

When a nameless temporary, not bound to any references, would be moved or copied into an object of the same type (ignoring top-level cv- qualification), the copy/move is omitted. When that temporary is constructed, it is constructed directly in the storage where it would otherwise be moved or copied to. When the nameless temporary is the argument of a return statement, this variant of copy elision is known as RVO, "return value optimization".

So the compiler has the option to ignore the copy (which in this case would act as an implicit cast to non-const type).

Dmitry Rubanovich
  • 2,471
  • 19
  • 27