Curly bracket constructors? - crystal-lang

While reading through the Crystal docs, I came across this line:
deq = Deque{2, 3}
So I think this calls the Deque.new(array : Array(T)) constructor. However, I did not find any documentation about this syntax whatsoever.
(EDIT: The documentation can be found here)
To test this way of calling constructors, I wrote the following test
class Foo(T)
def initialize(#bar : Array(T)); end
def to_s(io : IO)
io << "Foo: "
io << #bar
end
end
puts Foo{1} # Line 10
But, compiling it prints this error:
Error in line 10: wrong number of arguments for 'Foo(Int32).new' (given 0, expected 1)
Overloads are:
- Foo(T).new(bar : Array(T))
Which I really don't understand at all.
Foo(Int32){1} Raises the same error.
Question is, what is this Klass{1, 2, 3} syntax? And how do you use it?

They are documented here: https://crystal-lang.org/docs/syntax_and_semantics/literals/array.html
Array-like Type Literal
Crystal supports an additional literal for arrays and array-like types. It consists of the name of the type followed by a list of elements enclosed in curly braces ({}) and individual elements separated by a comma (,).
Array{1, 2, 3}
This literal can be used with any type as long as it has an argless constructor and responds to <<.
IO::Memory{1, 2, 3}
Set{1, 2, 3}
For a non-generic type like IO::Memory, this is equivalent to:
array_like = IO::Memory.new
array_like << 1
array_like << 2
array_like << 3
For a generic type like Set, the generic type T is inferred from the types of the elements in the same way as with the array literal. The above is equivalent to:
array_like = Set(typeof(1, 2, 3)).new
array_like << 1
array_like << 2
array_like << 3
The type arguments can be explicitly specified as part of the type name:
Set(Number) {1, 2, 3}

To create such a type you need to define an argless contructor and #<< method:
class Foo(T)
def initialize
#store = Array(T).new
end
def <<(elem : T)
#store << elem
end
end
foo = Foo(Int32){1, 2, 3}
p foo #=> #<Foo(Int32):0x103440b20 #store=[1, 2, 3]>

Related

How does auto deal with multiple values in () initializer?

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

sending multiple parameters to << operator [duplicate]

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.

How to combine operator << with the comma operator to assign multiple values to an array or vector class member? [duplicate]

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.

c++ - matrix initialization with comma-separated values

Hi I came across this piece of code. It demonstrates how to work with matrix structures of the dlib library.
According to this one can initialize a matrix structure by:
M = 54.2, 7.4, 12.1,
1, 2, 3,
5.9, 0.05, 1;
How can this be possible in C++?
Is this some kind of operator overloading?
Logic
This is possible by overloading operator, (operator comma), and for example make it push new floating point values into M.
A thing to notice is that operator, should always have at least one parameter of class type, therefore you'll have to create a class that is implicitly convertible to floating point value (for example via a non explicit constructor with 1 argument of type double or float).
Example
For example we'll try to do this for a wrapper type over an std::vector and we'll try to make M = 1, 2, 3, 4, 5 a valid expression which results in an std::vector with those elements in sequence. You'll see that this is easily applicable to the matrix example you gave.
A thing to remember is that operator= has more precedence over operator, (as shown in this table of operator precedence); therefore M = 1, 2, 3, 4, 5 will really be parsed as: (((((M = 1), 2), 3), 4), 5).
Given that, we'll start by creating our class container with an operator= that takes a single value and pushes it into the container:
template<typename ValueType>
struct container {
explicit container(std::size_t n) {
vec.reserve(n);
}
container& operator=(ValueType a) {
vec.push_back(a);
return (*this);
}
std::vector<ValueType> vec;
};
At this point we can define operator, as:
template<typename ValueType>
container<ValueType>& operator,(container<ValueType>& m, ValueType a) {
m.vec.push_back(a);
return m;
}
which just pushes back a new element value.
And now you can easily see that the following works just fine and prints 1 2 3 4 5:
int main() {
container<int> M(5);
M = 1, 2, 3, 4, 5;
for (auto i : M.vec) std::cout << i << ' ';
}
Live demo
Considerations
I discourage this technique as much as possible. It forces you to have weird semantic for other operators, such as operator= and really seem to add nothing to a simple use of std::initializer_list<T>.
A saner example would be to have operator= as follows:
container& operator=(std::initializer_list<ValueType> a) {
std::copy(begin(a), end(a), back_inserter(vec));
return (*this);
}
and then simply use brackets:
int main() {
container<int> M(5);
M = { 1, 2, 3, 4, 5 };
for (auto i : M.vec) std::cout << i << ' ';
}
Live demo

How could comma separated initialization such as in Eigen be possibly implemented in C++?

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.