Klemens Klemens - 2 months ago 16
C++ Question

Template class and nested class C++

I have a problem with

typename SnakeGame
. I would like to know how to make
SnakeGame
to global type in class
KeyboardEvents
. Now a nested class like
DirectionKeyboard
don't know what the type
SnakeGame
is, since it only sees see
KeyboardEvents<SnakeGame>
type. I don't know how to change it :P

Here's the error:

no know conversion for argument 1 from 'KeyboardEvents SnakeGame>&' to 'SnakeGame&'

I would really appreciate help .

keyboardEvents.hpp

#include<SFML/Graphics.hpp>

template <typename SnakeGame>
class KeyboardEvents {
public:
virtual ~KeyboardEvents() = default;

protected:
class DirectionKeyboardEvent{
public:
virtual ~DirectionKeyboardEvent() = default;
virtual void direction(SnakeGame&) = 0; // error no know conversion
};

class GoRight : public DirectionKeyboardEvent {
public:
void direction(SnakeGame& snakeObj) {
snakeObj.snake[0].xCoor+=1;
}
};

class GoRight : public DirectionKeyboardEvent {
public:
void direction(SnakeGame& snakeObj){
snakeObj.snake[0].xCoor += 1;
}
};

class GoLeft : public DirectionKeyboardEvent{
public:
void direction(SnakeGame& snakeObj){
snakeObj.snake[0].xCoor-=1;
}
};

class GoUp:public DirectionKeyboardEvent{
public:
void direction(SnakeGame& snakeObj){
snakeObj.snake[0].yCoor-=1;
}
};

class GoDown : public DirectionKeyboardEvent{
public:
void direction(SnakeGame& snakeObj){
snakeObj.snake[0].yCoor+=1;
}
};

std::map<sf::Keyboard::Key, std::shared_ptr<DirectionKeyboardEvent>> mapOfDirects;

void initializeDirectionMap() {
mapOfDirects[sf::Keyboard::Right] = std::shared_ptr< DirectionKeyboardEvent >(new GoRight);
mapOfDirects[sf::Keyboard::Left] = std::shared_ptr<DirectionKeyboardEvent>(new GoLeft);
mapOfDirects[sf::Keyboard::Up] = std::shared_ptr<DirectionKeyboardEvent>(new GoUp);
mapOfDirects[sf::Keyboard::Down] = std::shared_ptr<DirectionKeyboardEvent>(new GoDown);
}

void chooseMethodFromKeyboardArrows(sf::Keyboard::Key codeFromKeyboard) {
auto iterator = mapOfDirects.find(codeFromKeyboard);

if(iterator!=mapOfDirects.end()){
iterator->second->direction(*this);//left , right,up , down, pause
mainDirection=codeFromKeyboard;
} else {
mapOfDirects[mainDirection]->direction(*this);
}
}
};


Here's the class where I use
KeyboardEvents
~ snakeGame.hpp

#include"keyboardEvents.hpp"

class SnakeGame:public Screen, public KeyboardEvents<SnakeGame> {
public:
SnakeGame(int size=16, int width=15, int height=15, int timeDelay=60000)
: Screen(size, width, height), KeyboardEvents<SnakeGame>(), timeDelay(timeDelay) {}
};

Answer

In your try to call the DirectionKeyboardEvent::direction inside the KeyboardEvents class.

Even if you put a template parameter that happens to be the child class, there is no means to compiler can know in advance that KeyboardEvents<SnakeGame> will absolutely be extended by the class SnakeGame.

I mean, one could write this code:

KeyboardEvents<SnakeGame> keyboardEvents;

keyboardEvents.chooseMethodFromKeyboardArrows(/* some key */);

In that case, keyboardEvents is not related that much to SnakeGame. In fact there is no SnakeGame instance created at all! The compiler is right, the function chooseMethodFromKeyboardArrows that call direction is wrong to assume that a KeyboardEvents<SnakeGame> is a SnakeGame.

Inheritance work the other way around: a SnakeGame is indeed a KeyboardEvents<SnakeGame>. The other way is false.

I could show you how "to make it work", but a warning is needed here: you are overusing inheritance, and you used it the wrong way in the case of KeyboardEvent. You really should try to rearrange things around, or you'll end up in a real mess.


The solution "make it work"

Since you are using CRTP, you can tell the compiler that KeyboardEvents<SnakeGame> is indeed, in absolutely ALL cases, being extended by SnakeGame. If that's really the case, you can just static_cast your base class to the child class:

if(iterator!=mapOfDirects.end()){
    // Notice the presence of the cast here
    iterator->second->direction(static_cast<SnakeGame&>(*this));
    mainDirection=codeFromKeyboard;
}

The slightly better solution

You can as well using an existing instance of your snake class as parameter.

void chooseMethodFromKeyboardArrows(sf::Keyboard::Key codeFromKeyboard, SakeGame& game){
    auto iterator = mapOfDirects.find(codeFromKeyboard);

    if(iterator!=mapOfDirects.end()){
        iterator->second->direction(game);
        mainDirection=codeFromKeyboard;
    } else {
        mapOfDirects[mainDirection]->direction(game);
    }
}

However, the best idea is to not make SnakeGame extending KeyboardEvent, but to contain it in the class instead:

struct SnakeGame : Screen {
    KeyboardEvent<SnakeGame> event;

    void callEvents() {
        event.chooseMethodFromKeyboardArrows(/* some key */, *this);
    }
};

Here's an homework for you: Try to make the class KeyboardEvent not a template. I'm sure you can find a way to pass around your class without the use of themplates, while still accessing directly to your class SnakeGame, without casts or interfaces.

Comments