Does C++ ternary operator not work for initialization? [duplicate] - c++

My compiler is the latest VC++ 2013 RC.
int f(bool b)
{
return {}; // OK
return b ? 1 : { }; // C2059: syntax error : '{'
return b ? 1 : {0}; // C2059: syntax error : '{'
return b ? {1} : {0}; // C2059: syntax error : '{'
}
Why can braced-init-list not be used in ternary operator?
Is this behavior defined as ill-formed by the C++ standard, or just a bug of the VC++ compiler?

Well, here's what the standard says about the braced-init-list (8.5.3.1):
List-initialization
can be used
as the initializer in a variable definition (8.5)
as the initializer in a new expression (5.3.4)
in a return statement (6.6.3)
as a function argument (5.2.2)
as a subscript (5.2.1)
as an argument to a constructor invocation (8.5, 5.2.3)
as an initializer for a non-static data member (9.2)
in a mem-initializer (12.6.2)
on the right-hand side of an assignment (5.17)
Since this doesn't mention the conditional operator, I guess your compiler is right. Also note that the conditional operator expects expressions on the both sides of : (5.16), and as far as I understand, a brace-initializer is not an expression.

It's a syntax error. A braced-init-list is not an expression, and it doesn't have a type or value category. braced-init-list is available at various locations in the C++ grammar, and the operands of conditional expression are not one of those locations. Therefore your code fails to even parse.
If you want to do this:
struct T { ... };
T f(bool b)
{
return b ? {i,j,k} : {x,y};
}
instead you could do this:
T f(bool b)
{
return b ? T{i,j,k} : T{x,y};
}
and I believe, although this requires a move constructor, it wouldn't use it and RVO would kick in.
Alternatively you could of course do this:
T f(bool b)
{
if (b)
return {i,j,k};
else
return {x,y};
}
to get all the advantages of list-initialization.

Related

Default constructor expression and lvalues

My C++ colleagues and I ran into a curious construct:
struct A { int i; };
void foo(A const& a);
int main() {
foo(A() = A{2}); // Legal
}
The A() = A{2} expression completely befuddled us as it appears to be assigning A{2} to a temporary, default-constructed object. But see it in compiler explorer (https://gcc.godbolt.org/z/2LsfSk). It appears to be a legal statement (supported by GCC 9 and Clang 9), as are the following statements:
struct A { int i; };
int main() {
A() = A{2};
auto a = A() = A{3};
}
So it appears, then, that in some contexts A() is an lvalue. Or is something else going on here? Would appreciate some explanation and, preferably, a reference to the C++17 standard.
Update: #Brian found that this is a duplicate of assigning to rvalue: why does this compile?. But would really appreciate if someone could find the appropriate reference in the C++ standard.
A{} is always an rvalue per [expr.type.conv]
1 A simple-type-specifier or typename-specifier followed by a parenthesized optional expression-list or by a braced-init-list (the initializer) constructs a value of the specified type given the initializer.
If the type is a placeholder for a deduced class type, it is replaced by the return type of the function selected by overload resolution for class template deduction for the remainder of this subclause.
2 If the initializer is a parenthesized single expression, the type conversion expression is equivalent to the corresponding cast expression.
Otherwise, if the type is cv void and the initializer is () or {} (after pack expansion, if any), the expression is a prvalue of the specified type that performs no initialization.
Otherwise, the expression is a prvalue of the specified type whose result object is direct-initialized with the initializer.
If the initializer is a parenthesized optional expression-list, the specified type shall not be an array type.
emphasis mine
The reason these works is here is nothing in the standard to stop it from working.
For built in types like int there is [expr.ass]/1
The assignment operator (=) and the compound assignment operators all group right-to-left. All require a modifiable lvalue as their left operand; their result is an lvalue referring to the left operand.
So this stops you from doing int{} = 42;. This section doesn't apply to classes, though. If we look in [class.copy.assign] there is nothing that says that an lvalue is required, but the first paragraph does state
A user-declared copy assignment operator X​::​operator= is a non-static non-template member function of class X with exactly one parameter of type X, X&, const X&, volatile X&, or const volatile X&
Which means
A{} = A{2};
is actually
A{}.operator=(A{2})
Which is legal to do on an rvalue class object since the default operator = for your class has no ref-qualifier to stop it from being called on rvalues. If you add
A& operator=(const A& a) & { i = a.i; }
to A instead of using the default assignment operator then
A{} = A{2};
would no longer compile since the operator= will only work on lvalues now.

Implicit type conversion from initialization list compiles in one case but does not in another

I defined the following class:
class A {
int a;
public:
A(int _a = 0) :a(_a){}
A(initializer_list<int> il) :a(il.size()){}
friend A operator+(const A& a1, const A& a2);
};
A operator+(const A& a1, const A& a2){ return A(); }
The following client code
A a;
operator+(a,{3, 4, 5});
can compile, but the following
A a;
a + {3, 4, 5};
can not compile. The error message is "error C2059: syntax error : '{'" and "error C2143: syntax error : missing ';' before '{'".
Both two client codes try to do the implicit type conversion, from initialization list {3,4,5} to class A, but the first succeeds while the second snippet fails. I can not understand why.
Can you explain it?
I'm using MS Visual Studio 2013 Update 4.
[Edit]
Here is a follow up, after reading the comments and other related materials. I think my question can now be reduced to the following:
I got it that brace-init-list is not allowed in the RHS of an binary expression, and also that it IS allowed in a function call. The thing is, I think the binary expression, say, arg1 + arg2 is converted internally by the compiler to a function call operator+ (arg1, arg2), especially when arg1 is a class type. So at this point, there is no difference between binary expression and function call. As a result, the only explanation I can figure out is that there is a preventing rule applied before such a binary-expression-to-function-call conversion, that checks particularly whether the second argument is a brace-init-list or not. If it is, conversion to an equivalent function call will be forbidden and an error is produced. I wonder if these conjectures are real and if it is, is it written somewhere specifically in the C++ standard? Thank everyone who participated in my question.
The error message generated by clang is quite clear:
error: initializer list cannot be used on the right hand side of operator '+'
Looking into the C++11 standard, I find this (8.5.4/1 [dcl.init.list]):
Note: List-initialization can be used
as the initializer in a variable definition (8.5)
as the initializer in a new expression (5.3.4)
in a return statement (6.6.3)
as a function argument (5.2.2)
as a subscript (5.2.1)
as an argument to a constructor invocation (8.5, 5.2.3)
as an initializer for a non-static data member (9.2)
in a mem-initializer (12.6.2)
on the right-hand side of an assignment (5.17)
So I guess braced-init-list can only be used in the cases listed above. operator+(a, {1, 2, 3}) works because of case 4. a = {1, 2, 3} works because of the last case. But nothing is mentioned about a + {1, 2, 3}. So no.
I tried to look in the standard to find out the reason why it is not allowed to write expression + braced-init-list and the first I have found is a comment in an example
§5.1.2 (4)
a braced-init-list is not an expression
and it does not fulfill the requirements for a primary-expression. But it can be used in a postfix-expression:
postfix-expression:
simple-type-specifier braced-init-list
typename-specifier braced-init-list
Binary operators like + expect expressions to the left and right and thus, expression + braced-init-list does not fit into the c++-grammer.
But a braced-init-list can appear on the right-hand side of an assignment, since it expects an initializer-clause:
assignment-expression:
logical-or-expression assignment-operator initializer-clause
initializer-clause:
assignment-expression
braced-init-list
Thus, you could rewrite your example using a operator+=:
A operator+=(A& a1, const A& a2){ return A(); }
int main() {
A a;
a + A{3, 4, 5};
a += {3, 4, 5};
}

Why operator ?: lost lvalue-ness if second or third argument is throwing expression?

In C++ operator ?: can be an lvalue if second and third argument is lvalue of same type, for example:
int foo = 0, bar = 1;
bool condition = true;
(condition ? foo : bar) = 42; // correct
Also it is possible to throw something in this operator:
bar = (condition ? foo : throw 42); // still correct
But this code doesn't compile because left expression is not an lvalue:
(condition ? foo : throw 42) = bar; // compile error
I check this fact in C++ standart and read this:
The second or the third operand (but not both) is a throw-expression
(15.1); the result is of the type of the other and is an rvalue.
What explanation for this rule?
This is CWG defect 1560. Quoting the defect report:
A glvalue appearing as one operand of a conditional-expression in
which the other operand is a throw-expression is converted to a
prvalue, regardless of how the conditional-expression is used: [...]
This seems to be gratuitous and surprising.
This is fixed by the resolution of CWG 1550, now [expr.cond]/2.1 says:
[If] The second or the third operand (but not both) is a (possibly
parenthesized) throw-expression; the result is
of the type and value category of the other.
So it should work with a recent compiler.
throw has lower precedence than ?: and =. What you're effectively saying is
condition ? foo : throw (42 = bar);
Obviously 42 is not assignable as an rvalue.
Source: http://en.cppreference.com/w/cpp/language/operator_precedence
(Answer refers to syntax of original question...)

Why can I use operator= but not operator== with C++11 brace-initializers?

See this example:
struct Foo
{
int a;
int b;
bool operator == (const Foo & x)
{
return a == x.a && b == x.b;
}
};
int main ()
{
Foo a;
a = {1, 2};
if (a == {1, 2}) // error: expected primary-expression before ‘{’ token
{
}
}
The line a={1,2} is fine. The braces are convert to a Foo to match the argument type of the implicit operator= method. It still works if operator= is user-defined.
The line if (a=={1,2}}) errors as indicated.
Why does the expression {1,2} not convert to a Foo to match the user-defined operator== method?
List-initialization cannot be used as an argument to an operator in the general case. Per Paragraph 8.5.4/1 of the C++11 Standard:
[...] List-initialization can be used
— as the initializer in a variable definition (8.5)
— as the initializer in a new expression (5.3.4)
— in a return statement (6.6.3)
— as a for-range-initializer (6.5)
— as a function argument (5.2.2)
— as a subscript (5.2.1)
— as an argument to a constructor invocation (8.5, 5.2.3)
— as an initializer for a non-static data member (9.2)
— in a mem-initializer (12.6.2)
— on the right-hand side of an assignment (5.17)
The last item explains why list-initialization is allowed on the right side of operator =, even though it is not allowed in general for an arbitrary operator.
Because of the fifth item above, however, it can be used as an argument to a regular function call, this way:
if (a.operator == ({1, 2}))
It's just simply not supported.
Initializer lists are explicitly defined to be valid in initializations ([C++11: 8.5.4]), and assignments:
[C++11: 5.17/9]: A braced-init-list may appear on the right-hand side of
an assignment to a scalar, in which case the initializer list shall have at most a single element. The meaning of x={v}, where T is the scalar type of the expression x, is that of x=T(v) except that no narrowing conversion (8.5.4) is allowed. The meaning of x={} is x=T().
an assignment defined by a user-defined assignment operator, in which case the initializer list is passed as the argument to the operator function.
There is no standard wording to allow other, arbitrary cases.
If it were allowed, in this example, the type of {1,2} would be fairly ambiguous. It would be a complicated language feature to implement.
An explicit cast is required.
if (a == (Foo){1, 2})
{
}
When you are using the expression containing the user defined type a and == operator
Overloaded operator function gets called which as per definition you have given requires an argument of a reference of an object of class Foo
so your expression should be like a==b
where b is an object of class Foo
Here with this expression you will be able to compare data members of b and a and hence know if they are equal or not
No real reason.
C++ is the result of a committee effort so sometimes strange but deliberate decisions may slip through because of complex political/sociological dynamics.
C++ syntax is hard. Very hard. Almost unbelievably hard. There are rules even go like "if you can parse this arbitrarily long sequence of tokens as both this or that, then it's this".
It took many years for compilers even to simply agree on what is C++ and what is not.
In this case my wild guess is that they didn't like the idea that cases that looked very similar:
MyClass x = {...};
MyClass y; y = {...};
would be handled differently so there is a special provision for assignment to allow the syntax.
From a technical point of view I don't see what are the problems of allowing it for other operators too, and on the other hand if there are problems (e.g. for overloading, template instantiation etc.) I don't see how assignment can hope to escape them.
EDIT
g++ allows using not only strict operator= but also operator+=, operator-= and similar "augmented assignment". May be the logical problems happens only if you allow non-member overloads (that are forbidden for assignment and augmented assignment operators).

prefer conversion operator over conversion constructor

I have the following code snippet:
class A
{
public:
A() : x_(0), y_(0) {}
A(int x, int y) : x_(x), y_(y) {}
template<class T>
A(const T &rhs) : x_(rhs.x_), y_(rhs.y_)
{
}
int x_, y_;
};
class B
{
public:
B() {}
operator A() const { return A(c[0],c[1]); }
int c[2];
};
void f()
{
B b;
(A)b; // << here the error appears, compiler tries to use
// template<class T> A(const T &rhs)
}
Why compiler uses A's constructor? How can I make it use B's conversion operator to A?
I use MSVS2010 compiler. It gives me these errors:
main.cpp(9): error C2039: 'x_' : is not a member of 'B'
main.cpp(17) : see declaration of 'B'
main.cpp(28) : see reference to function template instantiation 'A::A<B>(const T &)' being compiled
with
[
T=B
]
main.cpp(9): error C2039: 'y_' : is not a member of 'B'
main.cpp(17) : see declaration of 'B'
UPD:
All right, implicit convert as Nawaz said really works. Let's make it more complicated, how make the following code work?
void f()
{
std::vector<B> b_vector(4);
std::vector<A> a_vector( b_vector.begin(), b_vector.end() );
}
UPD: A is the class in 3rd party lib which code I can't edit, so I can't remove A's converting constructor.
UPD: the simplest solution I've found for the moment is to define specialization of converting constructor for B. It can be done outside of 3rd party lib:
template<> A::A( const B &rhs ) : x_(rhs.c[0]), y_(rhs.c[1]) {}
The reason is not only because it thinks (A)b is same as A(b). The standard says this about explicit type-conversion (5.4):
The conversions performed by
a const_cast (5.2.11),
a static_cast (5.2.9),
a static_cast followed by a const_cast,
a reinterpret_cast (5.2.10), or
a reinterpret_cast followed by a const_cast,
can be performed using the cast
notation of explicit type conversion.
The same semantic restrictions and
behaviors apply.
Essentially this means that even for explicit type-conversion of (A)b (i.e. if you used ((A)b); to prevent from it being a variable declaration). it'd use the rules of static_cast. Now let's take a look at what the standard says about static_cast (5.2.9):
An expression e can be explicitly
converted to a type T using a
static_cast of the form
static_cast(e) if the declaration
“T t(e);” is well-formed, for some
invented temporary variable t (8.5).
The effect of such an explicit
conversion is the same as performing
the declaration and initialization and
then using the temporary variable as
the result of the conversion. The
result is an lvalue if T is a
reference type (8.3.2), and an rvalue
otherwise. The expression e is used as
an lvalue if and only if the
initialization uses it as an lvalue.
If you do static_cast<A>(b), it basically sees if A(b) is well-formed; and it is. Just because the actual instantiation of the template function copy-constructor fails, it doesn't make the actual declaration is ill-formed, hence it uses it and ultimately fails.
From 5.4/1 and 5.4/5 the C-cast picks the "best choice" C++ cast from a list. In this case, that's a static_cast.
Then from 5.2.9/2:
An expression e can be explicitly
converted to a type T using a
static_cast of the form
static_cast(e) if the declaration
“T t(e);” is well formed, for some
invented temporary variable t (8.5).
The effect of such an explicit
conversion is the same as performing
the declaration and initialization and
then using the temporary variable as
the result of the conversion. The
result is an lvalue if T is a
reference type (8.3.2), and an rvalue
otherwise. The expression e is used as
an lvalue if and only if the
initialization uses it as an lvalue.
So it picks the constructor before even attempting any other option.
In this case you've defined two conversions to get to the same end result, but the language has specific rules dictating that it will always use the available constructor. You should probably leave the constructor and change the operator to an explicit as-type function instead.
EDIT for OP's edit:
I don't think you'll be able to use the vector iter, iter constructor. You'll need to start with an empty vector and either use a for loop with push_back or use std::transform.
(A)b; // << here the error appears, compiler tries to use
This is explicit casting to A. Hence the constructor of A gets invoked to convert b to A.
A a(1,2);
a = b ; //this will invoke user-defined conversion of B (implicit conversion)
Demo : http://www.ideone.com/K9IxT
As mentioned, given the choice between a well-formed constructor and a conversion operator, the compiler will always call the constructor. But you could call the conversion operator directly, if you wish:
A a = b.operator A();
As for the second part of your question on how to make it working, I would use std::transform instead of modifying/adding something in the 3rd-party library namespace, unless only this library is well documented and it is intended that you should specialize this constructor:
a_vector.reserve( b_vector.size() );
std::transform( b_vector.begin(), b_vector.end(),
std::back_inserter( a_vector ), boost::bind( &B::operator A, _1 ) );
The syntax (A)b is the same as A(b), which is a construction. If you want a cast, then you must explicitly use static_cast, for example.