Ah, but you could write a Game class in your engine, one that is basically an abstract base class:
struct Game {
virtual void OnSetup() = 0;
virtual void OnUpdate (float elapsed) = 0;
virtual void OnRender (float elapsed) = 0;
virtual void OnShutdown () = 0;
};
Then you expect the "user" of the library to create code that subclasses the Game object, implementing each function to perform the appropriate actions. Then you expect your game loop to be given a pointer to the user's game object when it runs:
struct MyAwesomeGame : Game {
... implement functions...
};
int main ()
MyAwesomeGame game;
GameLoop::Run(game);
}
And your "run" method takes a Game&, which allows dynamic dispatch (you could do a Game* if you wanted, but a reference works and enforces the idea that you have to pass one, you can't pass nullptr). At the appropriate points in your "run" method you call the appropriate methods of the game object, e.g., you call OnStartup before you enter the loop proper, OnShutdown right before you return, and OnUpdate and OnRender during the actual loop.
The above is a simplified form of the idea (you'd probably want to take a closer look at what goes on in what is currently your m_pEngine->Update() calls and the like, but it gets the basic point across. In "real world" implementation you might use a non-virtual interface idiom to ensure that things that are "supposed" to happen on Update or Render happens regardless of what the user puts in their derived implementation. But essentially what you're doing here is using the game class to provide the basic required functionality of the game engine (this may be what your 'engine' class currently does) and use inheritance to allow user code to implement methods that are called at the right point to supply custom, game-specific behavior.
Then you have only the game-specific code in the game project, and the general part of the code (the Game base class and/or the Engine class, the GameLoop class, etc) in the engine library.