MarcusAseth

Questions about Templates

Recommended Posts

MarcusAseth    69

I was doing an experiment, code below:

template<typename T>
struct S
{
	explicit S(T v) :val{ v } {};
	T val;
};

int main()
{
	S<int> MyS('g');
	cout << MyS.val << endl;//Output is 103

	return 0;
}

Even though I am providing T with a type which is int, and I have an explicit constructor, why I have an implicit conversion from char to int? Shouldn't it see that T is int since I'm telling it so and thus give me error for constructing an S out of char? :S

 

Edited by MarcusAseth

Share this post


Link to post
Share on other sites
Kylotan    10007

The explicit keyword means that it won't create an instance of the object without you explicitly constructing one - for example, as part of an expression where it might normally make sense to the compiler to cast some object to this type, but where you want to disallow this.

Here, you do explicitly construct an object of the type, so it calls the constructor, and it makes the normal effort to convert the argument for you.

In this example, it's the 'int' that has an 'implicit' or 'converting' constructor, which magically creates an int out of a char. You don't get to change the semantics of ints, though.

To recap: explicit constructors don't say anything about their arguments, they say things about when they can be legitimately called.

Share this post


Link to post
Share on other sites
Kylotan    10007

Not grossly. You obviously understand the basic concept, of avoiding the implicit conversion, but you were just unsure which 'end' of the process it applied to. :)

Share this post


Link to post
Share on other sites
MarcusAseth    69

I'm changing the title of the topic into "Questions about Templates" because I'm experimenting with it and I know I will have more than one, doesn't seems right to make 1000 mini-topics :/

So now I have the code below:

template<typename T>
struct S
{
	S(T v) :val{ v } {};
	T val;
};

template <typename R, typename T>
R& get(T& e)
{
	return e.val;
}

int main()
{
	S<char> MySChar('B');
	cout << "MySChar: " << MySChar.val << endl;
	
	cout << "Returned val: " << get<char>(MySChar) << endl;

	return 0;
}

The function get is not part of the S class, as required from the book exercise (which is "" Add a function template get() that returns a reference to val.")

I don't like that I have to specify the return type when I call it, if I have 5 different instantiation of S it becomes messy, so I was trying to make it in such a way that I can just write get(MySChar) and it just works with wathever instantiation of S, but I am failing on it, code below:

template<typename T>
struct S
{
	S(T v) :val{ v } {};
	T val;
	typedef T type;
};

template <typename T, typename R>
R& get(T& e)
{
	using R = e.type;
	return e.val;
}

int main()
{
	S<char> MySChar('B');

	cout << "MySChar: " << MySChar.val << endl;

	cout << "Returned val: " << get(MySChar) << endl;

	return 0;
}

How would I do that the proper way? :S

 

Share this post


Link to post
Share on other sites
_Silence_    969
47 minutes ago, MarcusAseth said:

I was doing an experiment, code below:


template<typename T>
struct S
{
	explicit S(T v) :val{ v } {};
	T val;
};

int main()
{
	S<int> MyS('g');
	cout << MyS.val << endl;//Output is 103

	return 0;
}

Even though I am providing T with a type which is int, and I have an explicit constructor, why I have an implicit conversion from char to int? Shouldn't it see that T is int since I'm telling it so and thus give me error for constructing an S out of char?

 

 

In C++ words, this is called promotion. Promotions happen automatically.

See for example Integral promotion of this page.

Share this post


Link to post
Share on other sites
MarcusAseth    69

Well, I kind of found a solution to my last question (by throwing everything at random at it), which is auto& return type. But still out of curiosity, what would be the second best alternative to auto, using templates? :P

template <typename T>
auto& get(T& e)
{
	return e.val;
}

 

Edited by MarcusAseth

Share this post


Link to post
Share on other sites
MarcusAseth    69
15 minutes ago, Kylotan said:

but this is all too abstract for me to even work out what you're trying to do at a glance.

nothing too fancy, I'm just trying to make the code below compile without changing it but just by modifying that get() function 

The code below the commented line that says "//THIS 5 LINES BELOW NEEDS TO COMPILE"

int main()
{
	S<int> MySInt(5);
	S<char> MySChar('B');
	S<double> MySDouble(2.2);
	S<string> MySString(string("wow!"));
	S<vector<int>> MySVec(vector<int>(4, 99));

	cout << "MySInt: " << MySInt.val << endl;
	cout << "MySChar: " << MySChar.val << endl;
	cout << "MySDouble: " << MySDouble.val << endl;
	cout << "MySString: " << MySString.val << endl;

	cout << "MySVec: ";
	for (auto& e : MySVec.val)
		cout << e << " ";
	cout << endl;

	//THIS 5 LINES BELOW NEEDS TO COMPILE
	cout << "Returned val from get(): " << get(MySInt) << endl;
	cout << "Returned val from get(): " << get(MySChar) << endl;
	cout << "Returned val from get(): " << get(MySDouble) << endl;
	cout << "Returned val from get(): " << get(MySString) << endl;
	cout << "Returned val from get(): " << get(MySVec)[0] << endl;

	return 0;
}

So this is the function with decltype (assuming I am using it correctly) but doesn't work as well :/

template <typename T, typename R>
R& get(T& e)
{
	decltype(e.val) R;
	return e.val;
}

auto& as return type works just fine and those 5 lines compile, I was just wondering if there where any other ways :P 

Edited by MarcusAseth

Share this post


Link to post
Share on other sites
MarcusAseth    69

Moving to a new question, why is the code below not compiling if I uncomment one of the 4 commented-out lines ?

Function "set(Container, newValue, ID)"  takes a "MyContainer" class, a new value to set and an optional ID, just in case is a MyContainer<vector<int>>.

inside of it, takes the "val" of the MyContainer which returns wathever it is containing, in the example case is indeed a vector<int>&, but as soon as I try to use an operator<< or operator= on Vec, this thing doesn't compile...what am I not getting here? :S

template<typename T>
struct is_Container {
	static const bool value = false;
};

template<typename T, typename Alloc>
struct is_Container<vector<T,Alloc>> {
	static const bool value = true;
};

template <typename T, typename V>
void set(MyContainer<T>& e, V newVal, int ID = 0)
{
	T& Val = e.get();
	if (is_Container<T>::value)
	{
		cout << "is a vector" << endl;
		//cout << Val[ID] << endl;
		//Val[ID] = newVal;
	}
	else
	{
		cout << "is not a container" << endl;
		//cout << Val << endl;
		//Val = newVal;
	}
}

GcucCqV.png

Edited by MarcusAseth

Share this post


Link to post
Share on other sites
Kylotan    10007

When you say something is "not compiling", it's normally polite to give the error message. :) That usually tells you everything you need to know...

 

Share this post


Link to post
Share on other sites
MarcusAseth    69
14 minutes ago, Kylotan said:

When you say something is "not compiling", it's normally polite to give the error message. :) That usually tells you everything you need to know...

 

Good point, I didn't yet sorted out the problem of my VC being in Italian, but I'll paste the error link :P

I'm getting 2 Compiler Error C2109 on line 52 and 53 where I try to  use Val[ID] inside my "if" block

Quote

subscript requires array or pointer type

And 2 Compiler Error C2679 on line 58 and 59 where I try to use Val inside my "else" block

Quote

binary 'operator' : no operator found which takes a right-hand operand of type 'type' (or there is no acceptable conversion)

But from the screenshot is clearly visible that Val is a vector<int>& therefore I don't get it.. well, since it doesn't run it is failing at compile time or before, so I assume this is because at that time is not known what Val will be, and is only know later on. So how do I get it to "assume" that I will provide a valid variable with those operators (operator[], operator<<, operator=) at run-time?! x_x

Edited by MarcusAseth

Share this post


Link to post
Share on other sites
MarcusAseth    69

Ok, trough commenting and uncommenting I think I've realized what is going on here:

template <typename T, typename V>
void set(MyContainer<T>& e, V newVal, int ID = 0)
{
	T& Val = e.get();
	if (is_Container<T>::value)
	{
		cout << "is a vector" << endl;
		//cout << Val[ID] << endl;
		//Val[ID] = newVal;
	}
	else
	{
		cout << "is not a container" << endl;
		//cout << Val << endl;
		//Val = newVal;
	}
}

When set() is instantiated with a first parameter that contain a MyContainer of basic type let's say int, then T is int and the expressions 

Quote

//cout << Val[ID] << endl;
        //Val[ID] = newVal;

becomes totally invalid (even though the control flow would never send us there).

On the other hand when I instantiate it with a MyContainer<vector<int>> as first parameter, then the expressions  

Quote

//cout << Val << endl;
 //Val = newVal;

make no sense at all. (cout of vector<int> and assignment of int to vector<int>

Working with templates can be confusing, it seems :P

How is this situation handled? Should Val be passed down to a function template with partial specialization for vectors and non vectors? I think that would work, but can't be too sure with this stuff :P

 

Edited by MarcusAseth

Share this post


Link to post
Share on other sites
MarcusAseth    69

Function overloading did it, so that must be the way to go, (I hope)

This is actually cool, because I can direct the flow of things toward the no inplicit conversion function overload! :D 

I'm discovering hot water here xD

template <typename T, typename V>
void set(MyContainer<T>& e, V newVal, size_t ID = 0)
{
	cout << "MyContainer specialization called" << endl;
	T& Val = e.get();
	set(Val, newVal, ID);
}

template <typename T>
void set(vector<T>& Val, T newVal, size_t ID = 0)
{
	cout << "vector specialization called" << endl;
	Val[ID] = newVal;
}
template <typename T, typename V>
void set(vector<T>& Val, V newVal, size_t ID = 0)
{
	cout << "NO IMPLICIT CONVERSION called" << endl;
}


template <typename T, typename V>
void set(T& Val, V newVal, size_t ID = 0)
{
	cout << "base type specialization called" << endl;
	Val = newVal;
}
int main()
{
	MyContainer<int> MySInt(5);
	MyContainer<vector<int>> MySVec(vector<int>(4, 99));

	cout << "MySInt: " << get(MySInt) << endl;
	set(MySInt, 22);
	cout << "MySInt: " << get(MySInt) << endl << endl << endl;

	cout << "MySVec: " << get(MySVec)[2] << endl;
	set(MySVec, 3.3, 2);
	cout << "MySVec: " << get(MySVec)[2] << endl << endl << endl;

	cout << "MySVec: " << get(MySVec)[2] << endl;
	set(MySVec, 64, 2);
	cout << "MySVec: " << get(MySVec)[2] << endl << endl << endl;
	return 0;
}

Output:

Quote

MySInt: 5
MyContainer specialization called
base type specialization called
MySInt: 22


MySVec: 99
MyContainer specialization called
NO IMPLICIT CONVERSION called
MySVec: 99


MySVec: 99
MyContainer specialization called
vector specialization called
MySVec: 64

 

Edited by MarcusAseth

Share this post


Link to post
Share on other sites
matt77hias    436
3 hours ago, MarcusAseth said:

S(T v) :val{ v }

I would just use the non-braced initializer list:

S(T v) :val(v)

I only use braced initializer list for zero-initializing arrays. Due to type deductions in C++11/14, braced initializer lists can be tricky.

FYI: setting up VS2017 can be cumbersome (you need lots of LMB clicks) for small C++ tests. You can use the Visual C++ compliant single header web compiler: http://webcompiler.cloudapp.net/.

Edited by matt77hias

Share this post


Link to post
Share on other sites
MarcusAseth    69
11 minutes ago, matt77hias said:

I would just use the non-braced initializer list:


S(T v) :val(v)

 

curly braces are the safest way, right?

If in there I'm initializing member variables, then { } prevents me to pass argument that would require an implicit conversion and possible truncation, so I just use those always and I never have to think about it because if I make a mistake, then things stop working right away :P

Edited by MarcusAseth

Share this post


Link to post
Share on other sites
matt77hias    436
9 minutes ago, MarcusAseth said:

curly braces are the safest way, right?

If in there I'm initializing member variables, then { } prevents me to pass argument that would require an implicit conversion and possible truncation, so I just use those and I never have to thing about it becuase if I make a mistake, then things stop working immediately :P

Safe? I guess (although in combination with std::initializer_list constructors?).

Obvious? No.

int a[3] = {1};
cout << a[0] << ',' << a[1] << ',' << a[2] << endl; 

 

What do you mean with the "prevention of passing arguments that would require implicit conversion"?

Edited by matt77hias

Share this post


Link to post
Share on other sites
MarcusAseth    69
28 minutes ago, matt77hias said:

Safe? I guess.

Obvious? No.


int a[3] = {1};
cout << a[0] << ',' << a[1] << ',' << a[2] << endl; 

 

Well, honestly, you example above to me seems obvious though :S

an array of 3 elements taking an initializer list, the initializer list to the right sets the first element to 1.

Quote

What do you mean with the "prevention of passing arguments that would require implicit conversion"?

I mean that the code below won't compile because it requires a conversion from double to int

	int a[3] = { 1.4 };
	cout << a[0] << ',' << a[1] << ',' << a[2] << endl;

If you accidentally passed a float variable you would have lost precision without noticing maybe, that's why I think is valuable to prefer it to the more loose ( )

EDIT: I compiled my example just to be sure, apparently it works, I must have got it wrong from the book o_O

Need to check.

EDIT2: ok, the safety I mentioned won't apply when you use it to initialize an array it seems, but it still work on all the future uses, and I think some safety is better than no safety at all (and it comes for free, so...I'll take it!) :D

Edited by MarcusAseth

Share this post


Link to post
Share on other sites
matt77hias    436
1 hour ago, MarcusAseth said:

EDIT2: ok, the safety I mentioned won't apply when you use it to initialize an array it seems, but it still work on all the future uses, and I think some safety is better than no safety at all (and it comes for free, so...I'll take it!) 

99% of my non-default/non-move/non-copy constructors are explicit, so except for implicit primitive-to-primitive conversions, I personally prefer calling the constructor straight away with () instead of {} (which calls a std::initializer_list constructor if present) in the constructor initializer list. Furthermore, my compiler uses the highest (reasonable, so not Wall) warning level to notify me of possible losses of precision due to for instance implicit primitive-to-primitive conversions.

Edited by matt77hias

Share this post


Link to post
Share on other sites
MarcusAseth    69

More template stuff.

I have some doubts regarding template, still. This compile and works, and if I understand it right, I need the operator overloads to be templates in case I try to do something like "Number<int>(3) + Number<float>(4.15)" which are two different type.

If I have those operator overloads just taking a plain Number& as argument, then things won't compile. 

And yet, operator>> and operator<< just work fine with a Number&, how does that make sense?!

EDIT: while I was writing this question, I thought that maybe is because inside a particular template instantiation itself, Number is defined to be the unique type for that Number<T>, therefore by saying Number I am actually calling the unique type of that particular instantiation?

If that was the case I could then just have a  return type Number& in the operator overloads instead of return type Number<T>& , can you guys confirm this is the case? :S

template<typename T>
class Number {
	T val;
public:
	//Constructors
	Number() :val{ 0 } {}
	Number(T v) :val{ v } {}

	//Methods
	T& get() { return val; }

	//Operators
	template<typename U>
	Number<T>& operator+(Number<U>& rhs) { val += rhs.get(); return *this; }
	template<typename U>
	Number<T>& operator-(Number<U>& rhs) { val -= rhs.get(); return *this; }
	template<typename U>
	Number<T>& operator*(Number<U>& rhs) { val *= rhs.get(); return *this; }
	template<typename U>
	Number<T>& operator/(Number<U>& rhs) { val /= rhs.get(); return *this; }

	friend ostream& operator<<(ostream& stream, Number& rhs) { stream << rhs.val; return stream; }
	friend istream& operator>>(istream& stream, Number& rhs) { stream >> rhs.val; return stream; }
};

int main()
{
	Number<double> myDouble(3.4);
	myDouble - Number<int>(6.6);
	cout << myDouble << endl << endl;
	
	cin >> myDouble;
	cout << myDouble << endl;

	return 0;
}

 

Share this post


Link to post
Share on other sites
Kylotan    10007

Again, what are the compiler errors you get?

These operators are wrong anyway, because they are mutating the left-hand side when they should be returning a new value. If you want += semantics, you should define a += operator.

Share this post


Link to post
Share on other sites
MarcusAseth    69
9 minutes ago, Kylotan said:

Again, what are the compiler errors you get?

No compile errors, quoting my message above:

Quote

And yet, operator>> and operator<< just work fine with a Number&, how does that make sense?

 

Share this post


Link to post
Share on other sites
Lactose    11471
2 minutes ago, MarcusAseth said:

No compile errors

 

28 minutes ago, MarcusAseth said:

If I have those operator overloads just taking a plain Number& as argument, then things won't compile.

 

Share this post


Link to post
Share on other sites
MarcusAseth    69

@Lactose Maybe I didn't explained myself properly, I am not considering that as the error, that's why is not even in my code example, and that makes sense because as I said

Quote

 I need the operator overloads to be templates in case I try to do something like "Number<int>(3) + Number<float>(4.15)" which are two different type.

In that case the compile error is obviously I have no operator overload between the 2 different types, if it was Number&.

The question is another one...and is about the absence of ant kind of compile errors :D

 

Edited by MarcusAseth

Share this post


Link to post
Share on other sites
Kylotan    10007

Sorry, you're talking in hypotheticals. Can you form a smaller, precise example of the problem? i.e.

  1. What are you doing?
  2. What do you expect or want to happen?
  3. What actually happens?

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now


  • Similar Content

    • By MarcusAseth
      I think I am having the problem mentioned in the title, but I can't be sure of it as well...
      Got 3 classes, GameMode, Entity, Paddle. 
      Paddle Inherit from Entity, and both Paddle and Entity need to #include "GameMode.h" in order to pass down the constructor a GameMode* and store it inside of every Entity.
      I pretty much forward declared everything inside everything else but stuff just won't compile, the error I am getting is C2504 "Entity: base class undefined" .
      I'll paste below the .h and .cpp for the Entity class, and only .h for the other two. Can someone tell me what am I doing wrong?
      Entity.h
      #pragma once #include "SDL2\SDL.h" #include "GameMode.h" #include "Utility.h" using namespace util; class GameMode; enum class PivotMode: Uint8 {CENTER,TOP_LEFT}; class Entity { protected://variables float XCenter; float YCenter; SDL_Rect CollisionBox; SDL_Texture* Sprite; GameMode* Game; SDL_Renderer* Renderer; public://constructors Entity(GameMode* gameRef, PivotMode inputMode, int x, int y, std::string path); virtual ~Entity(); Entity(const Entity&) = delete; Entity& operator=(const Entity&) = delete; Entity(Entity&&) = delete; Entity& operator=(Entity&&) = delete; public://methods virtual void Update(float deltaTime) = 0; virtual void Draw(float interpolation) = 0; private://methods SDL_Texture* RequestTexture(std::string path)const; void SetCollisionBox(int x, int y, PivotMode InputMode); }; Entity.cpp
      #include "Entity.h" Entity::Entity(GameMode* gameRef, PivotMode inputMode, int x, int y, std::string path) :Game{ gameRef }, XCenter{ static_cast<float>(x) }, YCenter{ static_cast<float>(y) } { if (Game) { Renderer = Game->GetRenderer(); } Sprite = RequestTexture(path); if (Sprite) { SetCollisionBox(x, y, inputMode); } } Entity::~Entity() { } SDL_Texture* Entity::RequestTexture(std::string path)const { if (Game->IsRunning()) { return Game->RequestTexture(path); } return nullptr; } void Entity::SetCollisionBox(int x, int y, PivotMode InputMode) { SDL_QueryTexture(Sprite, nullptr, nullptr, &CollisionBox.w, &CollisionBox.h); switch (InputMode) { case PivotMode::CENTER: { CollisionBox.x = x - CollisionBox.w / 2; CollisionBox.y = y - CollisionBox.h / 2; XCenter = static_cast<float>(x); YCenter = static_cast<float>(y); }break; case PivotMode::TOP_LEFT: { CollisionBox.x = x; CollisionBox.y = y; XCenter = static_cast<float>(x) + CollisionBox.w / 2; YCenter = static_cast<float>(y) + CollisionBox.h / 2; }break; } } Paddle.h
      #pragma once #include "Entity.h" #include "GameMode.h" class GameMode; enum class PivotMode:Uint8; class Paddle : public Entity { public://methods Paddle(GameMode* gameRef, PivotMode inputMode, int x, int y, std::string path); ~Paddle(); virtual void Update(float deltaTime); virtual void Draw(float interpolation); }; GameMode.h
      #pragma once #include "SDL2\SDL.h" #include <string> #include <vector> #include <memory> #include "App.h" #include "Entity.h" #include "Paddle.h" class Entity; class Paddle; class GameMode { friend class App; private://variables bool Running; SDL_Window* Window; SDL_Renderer* Renderer; App* AppRef; public://constructors GameMode(SDL_Window* Window, SDL_Renderer* Renderer, App* App); ~GameMode(); GameMode(const GameMode&) = delete; GameMode& operator=(const GameMode&) = delete; GameMode(GameMode&&) = delete; GameMode& operator=(GameMode&&) = delete; public://methods SDL_Texture* RequestTexture(std::string path)const; bool IsRunning()const; SDL_Renderer* GetRenderer()const; private://methods void Update(float deltaTime); void Draw(float interpolation); };  
    • By Jan Haas
      Hello,
      I just found out about https://github.com/shader-slang/slang. It's a shader language and accompanied by a library that is supposed to make it easier to work with modular shaders.
      What do you think about that? 
      Here is the paper that describes the concept: http://graphics.cs.cmu.edu/projects/shadercomp/he17_shadercomp.pdf
      Thanks in advance
       
    • By Shaarigan
      So here again with some more opinion/discussion related topic about what features should/are normally be related into a game engine's core system from the implementation side of view. With a huge time I invested into writing and even more into research for my very own game engine project, there were as many differences as there are to coding guidelines. Especially open source engines (Urho3D, Lumberyard) and those that offer there source code (Unreal, CryEngine, Phyre) change during the centuries depending on current technical, coding and hardware standards. They all consist of third party code and libraries that are set apart from the core system bur rely on it.
      Now I'm on a point where I want restructure/cleanup my project(s), removing obsolete and integrate prototyping code as same as change the growing code base to have a better clean managed file structure. The question that has had to emerge for long or short is what parts of the code base should be a core feature in order to keep the core system clean and flexible. Again reading many cross references it pointed some similarity out but also huge differences (like Unreals hashing/encryption code beeing in core) so I made a list of important stuff any game needs (I left out platform specifics and audio/renderer here because they are platform dependent and should stay in an own module/system)
      Allocator (memory management) Math (Vector, Matrix, Quaternion ...) Threading (Threading, Locks) Container (apart from the STL, Queue, Buffers, certain types of Array ...) String (management, encoding, utils) Input (seen a game without input?) IO (reading/writing files and manage filesystem structure) Type Handling And a list that is optional but may/may not be inside the core code
      Logging (because it is a development tool) Profiler (see logging) Serialization Plugins Events In my opinion, logging and profiler code is a heavyly used feature in development but is stripped out mostly when deploying a release version, not all games rely on events rather than using other interaction (like polling, fixed function loops) and also the same for plugins and serializing/deserializing data; so what do you think should also be a must have in core system or should go from the list into core system as well as a must have? What else should go outside as an own module/system (into an own subfolder inside the engine project's code base)?
      Courious to read your opinions
    • By DKdowneR
      Hi! I made a tile map reading from a file. Almost everything works good, but when a player go out of map, program runs into an error and says that "vector subscript out of range". My question is how to make check for it
      Drawing map : 
      void GameplayScreen::DrawMap(SDL_Renderer *renderer) { for (int y = map.size() - 1; y >= 0; --y) { for (int x = getStartBlockX(), xEnd = getEndBlockX(); x < xEnd && x < map[y].size(); ++x) { if (map[y][x] != "0,0") { int tempX = atoi(map[y][x].substr(0, map[y][x].find(',')).c_str()); int tempY = atoi(map[y][x].substr(map[y][x].find(',') + 1).c_str()); srcRect.x = tempX * 32; srcRect.y = tempY * 32; srcRect.w = 32; srcRect.h = 32; destRect.x = x * 32 + posX; destRect.y = (y * 32 + posY); destRect.w = 32; destRect.h = 32; vBlock[Earth]->Draw(renderer, srcRect, destRect); } } } } getStartBlockX returns first map block, getEndBlockX returns last, so it's like render on screen only a piece of map, not all blocks.
      tempX returns x coordinate of tile image,  tempY y coordinate. So, for example, if map is like :

      0,0 0,0 0,0 0,0 0,0
      0,0 0,0 0,0 0,0 0,0
      1,0 2,0 0,3 1,0 1,0
      0,0 is first block image, 1,0 is one next to the first, 0,3 is 2 under the first block etc.
         
  • Popular Now