Im in the middle of creating an application with openSG and C++.
Can anyone tell me, why these lines(174ff):
Vec3f snakeDirection = (1,2,3);
std::cout << "direction"<< snakeDirection<<"\n";
prints
direction 3,0,0
the complete code for my scene can be found here:
http://pastie.org/9420616#20,177
In C++ the expression (1,2,3) forces the compiler to interpret the commas inside the parentheses as the sequential-evaluation operator.
The sequential-evaluation operator is a binary operator that evaluates its first operand as void and discards the result, it then evaluates the second operand and returns its value and type. Therefore, the expression (1,2,3) is going to be evaluated in the following way:
First 1 is evaluated and discarded, then (2,3) is evaluated and the result (2,3) is returned.
First 2 is evaluated and discarded, then 3 is evaluated and the result 3 is returned.
Consequently, the evaluation of the expression (1,2,3) will return 3.
Thus, stating:
Vec3f snakeDirection = (1,2,3);
is the same as stating:
Vec3f snakeDirection = 3;
What you've probably intended to write is either:
Vec3f snakeDirection = Vec3f(1, 2, 3);
or
Vec3f snakeDirection(1, 2, 3);
or
Vec3f snakeDirection{1, 2, 3};
or
Vec3f snakeDirection = {1, 2, 3};
Related
I know for C++11 way of initializing a vector using auto, actually an std::initializer_list is initialized instead of a vector.
However, given below piece of code:
#include <iostream>
#include <vector>
using namespace std;
int main() {
auto x = {1, 2};
cout << typeid(x).name() << endl;
auto z = (1, 2);
cout << z << ", type: " << typeid(z).name() << endl;
return 0;
}
I don't understand:
Why the type of x returned is St16initializer_listIiE and the type of 'z' returned is 'i', using gcc-10 compiler. Shouldn't we just return std::initializer_list and 'int'?
There is a warning on z: warning: left operand of comma operator has no effect [-Wunused-value]. Then the 2nd half of result is: 2, type: i. How does c++11 interpret ()-initialized type? Why is only the last element passed into z and thus z is still of type int?
The only thing that makes an initializer list is {}. In
auto z = (1, 2);
what you have is the comma operator which only returns that last value. So that means your code boils down to
auto z = 2;
and since 2 is an int, z is an int.
Why the type of x returned is St16initializer_listIiE and the type of 'z' returned is 'i', using gcc-10 compiler. Shouldn't we just return std::initializer_list and 'int'?
typeid() won't directly give what you would expect, it just returns the specific type identification as written down in its code. If you would like to decrypt the same, pass it via c++filt:
c++filt -t St16initializer_listIiE
It will result in what you were expecting, i.e. an:
std::initializer_list<int>
There is a warning on z: warning: left operand of comma operator has no effect [-Wunused-value]. Then the 2nd half of result is: 2, type: i. How does c++11 interpret ()-initialized type? Why is only the last element passed into z and thus z is still of type int?
( ) is an initializer holding an expression, or a list of expressions. If you were to assign that to auto, it would select the very last item in the expression list as its type as it would be seperated by commas till the advent of the last expression, which ideally tells auto to assign its type to the last one.
For Example:
auto x = (1, 1.5L);
would result in a long.
auto x = (1, 1.5f);
would result in a float.
auto x = (1, "string");
would result in a const char pointer.
It entirely neglects the first value within the ( ) initializer, which is an int.
Because { 1, 2 } is an std::initializer_list<int>, but (1, 2) is an expression, that expands to comma-operator (it evaluates both arguments and returns the second one as a result, so (1, 2) is collapsed to (2), which is collapsed into 2. That's the reason, why auto z = (1, 2); evaluates into an integer initialization.
Because the result of instruction 1 is simply ignored (remember (1, 2) calculates both expressions and throws away the result of the first one).
Here's a part of Eigen documentation:
Matrix3f m;
m << 1, 2, 3,
4, 5, 6,
7, 8, 9;
std::cout << m;
Output:
1 2 3
4 5 6
7 8 9
I couldn't understand how could all the comma separated values be captured by operator<< above. I did a tiny experiment:
cout << "Just commas: ";
cout << 1, 2, 3, 4, 5;
cout << endl;
cout << "Commas in parentheses: ";
cout << ( 1, 2, 3, 4, 5 );
cout << endl;
Predictably (according to my understanding of C++ syntax) only one of the values was captured by operator<< :
Just commas: 1
Commas in parentheses: 5
Thus the title question.
The basic idea is to overload both the << and the , operators.
m << 1 is overloaded to put 1 into m and then returns a special proxy object – call it p – holding a reference to m.
Then p, 2 is overloaded to put 2 into m and return p, so that p, 2, 3 will first put 2 into m and then 3.
A similar technique is used with Boost.Assign, though they use += rather than <<.
This is a possible simplified implementation
struct M3f {
double m[3][3];
struct Loader {
M3f& m;
int i;
Loader(M3f& m, int i) : m(m), i(i) {}
Loader operator , (double x) {
m.m[i/3][i%3] = x;
return Loader(m, i+1);
}
};
Loader operator<<(double x) {
m[0][0] = x;
return Loader(*this, 1);
}
};
The idea is that << returns a Loader instance that waits for second element, and each loader instance uses the comma operator to update the matrix and returns another loader instance.
Note that overloading the comma operator is generally considered a bad idea because the most specific characteristic of the operator is strict left-to-right evaluation order. However when overloaded this is not guaranteed and for example in
m << f(), g(), ...
g() could end up being called before f().
Note that I'm talking about the evaluation order, not about associativity or precedence that are of course maintained also for overloaded versions.
For example g() could be called before f() but the result from f() is guaranteed to be correctly placed in the matrix before the result from g().
The comma itself is an operator in c++ which can be overloaded (and apparently is by eigen). I don't know the exact way eigen implements the overloading but I am sure that you can search for the sources of eigen to look it up.
To your little experient you have to understand how the unoverloaded comma operator works in c++.
The comma operator has the form <statement>,<statement> and is evaluated to whatever the second statement is evaluated to. The operator << has a higher precedence than the operator ,. Because of that the cout is evaluated before the rest of the comma operations are evaluated.
Because , is Left-to-right associative the code (1,2,3,4,5) is equal to ((((1,2),3),4),5) which evaluates to the most right value, which is 5.
Let's say I am developing a class Vec<T>, which represents a mathematical vector with elements of type T.
For convenience I created the constructor that takes std::initializer_list:
Vec(std::initializer_list<T> l)
: size(l.size())
{
data = new T[size];
std::copy(l.begin(), l.end(), data);
}
So now I can do the following things:
v += Vec<int>({ 1, -1 });
bool equal = (v == Vec<int>({ 8, 9 }));
And so on... However, it would be great if I could write it even shorter and cleaner:
v += { 1, -1 };
bool equal = v == { 8, 9 };
How do I achieve such behavior? I suppose I could overload a type-conversion operator to std::initializer_list, but is it even considered normal practice? How bad is it for compilation time and performance, and does it even work?
What about std::vector, does it support something like that in C++11?
EDIT:
So, here's my operator+=:
Vec<T> & operator+=(const Vec<T> &v)
{
assert(size == v.size);
for (int i = 0; i < size; ++i)
data[i] += v.data[i];
return *this;
}
The answers in the comments are correct, initializer_list actually works after assignment operators:
v += {2, 3};
v -= {2, 3};
I was trying to write something like this:
v = v + {2, 3};
That's why it didn't work.
So, the conclusion is: you can use it like this after assignment and compound-assignment operators, but for binary arithmetic operators and comparisons it won't work, am I correct? I suppose creating custom literal is not an option either.
By the way, how about:
const Vec<float> a{ 1.01, 2.02 }; // error: conversion from 'double' to 'float' requires a narrowing conversion test_app
const Vec<float> b{ 1.01f, 2.02f }; // works, obviously
Can I do something to enable implicit conversion in the first case?
EDIT2
Here is the operator+:
friend Vec<T> operator+(Vec<T> v, const Vec<T> &w)
{
v += w; // reuse compound assignment
return v; // return the result by value (uses move constructor)
}
Unfortunately, v + {1, 2} is not a well-formed expression in C++ for grammatical rules. Most binary operators only take expressions for each of the operands, and the braced list is not an expression. The compound assignment operator += is special, because assignment also accepts an initializer-clause for the right-hand operand, so v += {1, 2} happens to work.
Some alternatives:
operator+(v, {1, 2})
v + Vec<int>({1, 2})
How about v.equals({8, 9})? Also, while v + {1, 2, 3, 4} is not possible, you might still overload the comma operator as many vector-matrix implementations do (not for int, but for a specific type, like: v + (V(1), 2, 3, 4), add macros if you like them).
Here's a part of Eigen documentation:
Matrix3f m;
m << 1, 2, 3,
4, 5, 6,
7, 8, 9;
std::cout << m;
Output:
1 2 3
4 5 6
7 8 9
I couldn't understand how could all the comma separated values be captured by operator<< above. I did a tiny experiment:
cout << "Just commas: ";
cout << 1, 2, 3, 4, 5;
cout << endl;
cout << "Commas in parentheses: ";
cout << ( 1, 2, 3, 4, 5 );
cout << endl;
Predictably (according to my understanding of C++ syntax) only one of the values was captured by operator<< :
Just commas: 1
Commas in parentheses: 5
Thus the title question.
The basic idea is to overload both the << and the , operators.
m << 1 is overloaded to put 1 into m and then returns a special proxy object – call it p – holding a reference to m.
Then p, 2 is overloaded to put 2 into m and return p, so that p, 2, 3 will first put 2 into m and then 3.
A similar technique is used with Boost.Assign, though they use += rather than <<.
This is a possible simplified implementation
struct M3f {
double m[3][3];
struct Loader {
M3f& m;
int i;
Loader(M3f& m, int i) : m(m), i(i) {}
Loader operator , (double x) {
m.m[i/3][i%3] = x;
return Loader(m, i+1);
}
};
Loader operator<<(double x) {
m[0][0] = x;
return Loader(*this, 1);
}
};
The idea is that << returns a Loader instance that waits for second element, and each loader instance uses the comma operator to update the matrix and returns another loader instance.
Note that overloading the comma operator is generally considered a bad idea because the most specific characteristic of the operator is strict left-to-right evaluation order. However when overloaded this is not guaranteed and for example in
m << f(), g(), ...
g() could end up being called before f().
Note that I'm talking about the evaluation order, not about associativity or precedence that are of course maintained also for overloaded versions.
For example g() could be called before f() but the result from f() is guaranteed to be correctly placed in the matrix before the result from g().
The comma itself is an operator in c++ which can be overloaded (and apparently is by eigen). I don't know the exact way eigen implements the overloading but I am sure that you can search for the sources of eigen to look it up.
To your little experient you have to understand how the unoverloaded comma operator works in c++.
The comma operator has the form <statement>,<statement> and is evaluated to whatever the second statement is evaluated to. The operator << has a higher precedence than the operator ,. Because of that the cout is evaluated before the rest of the comma operations are evaluated.
Because , is Left-to-right associative the code (1,2,3,4,5) is equal to ((((1,2),3),4),5) which evaluates to the most right value, which is 5.
I have a struct defined with a constructor as such:
struct Point {
double x; double y;
Point (double xx=0, double yy=0): x(xx),y(yy){};
};
I can create points like this and this work fine,
Point P0(0,0), P1(1,1);
But I want to be able to modify it as such:
P0 = (4,5); //no compilation error but garbage returned when printing P0
Does not work!
Also, I have a function like I am using like this
void Foo(Point P0, Point P1){
...do stuff
}
and I want to pass the following argument like this:
Foo((1,2),(2,3)); //no compilation error but garbage returned
I tried looking all this up not really clear to me.. any help
P0 = (4,5);
This does not do what you think it does.
The expression (4,5) evaluates to 5. This is because it contains what's called the comma operator. The comma operator first evaluates its left operand, throws away the result, and then evaluates its second operand. So this statement is really equivalent to
P0 = 5;
Now, this is in turn equivalent to
P0 = Point(5);
which is equivalent to
P0 = Point(5, 0);
since you have a default argument. It's not garbage at all. It's perfectly well-defined.
Precisely the same thing is going to happen in your other statement:
Foo((1,2),(2,3)); // same as Foo(Point(2, 0), Point(3, 0));
To fix this, you can construct the points explicitly:
P0 = Point(4, 5);
Foo(Point(1, 2), Point(2, 3));
or, if you have C++11 support, you can do this:
P0 = {4, 5};
Foo({1, 2}, {2, 3});
(When a comma occurs inside braces, it's not a comma operator. It separates elements of the initializer list.)
(4,5) is equivalent to just 5, since that's how the comma operator works: it evaluates the first expression, discards the result, then evaluates the second. You should get a compilation warning about a discarded expression with no side-effects, if you enable warnings.
In C++11, you can use a list initialiser to do what you want:
P0 = {4,5};
Foo({1,2},{2,3});
If you're stuck with an older dialect, you'll have to spell it out:
P0 = Point(4,5);
Foo(Point(1,2), Point(2,3));
In this statement
P0 = (4,5); //no compilation error but garbage returned when printing P0
expression (4,5) is an expression with the comme operator. Its value is the second subexpression that is 5.
Then the compiler tries to convert value 5 to an object of type Point that to assign it to P0. As you have conversion constructor
Point (double xx=0, double yy=0): x(xx),y(yy){};
then the compiler calls it with arguments 5 and 0 that is as Point( 5, 0 ). This temporary object is assigned to P0.
You should use braced init list instead of the comma operator. For example
P0 = { 4, 5 };
provided that your compiler supports this feature of the C++ 11.
Otherwise you have to specify explicitly the constructor in the right side of the expression
P0 = Point( 4, 5 );
You should use P0 = Point(4, 5) instead. Point(4, 5) call creates a temporary object which is then copied to P0 (in fact it is usually optimized so no actual copying is required).