Looking back at the sokol headers (written in plain old C), which are my ‘longest actively maintained project’ (10 years next year), and in comparison to the millions(?) of lines of C++ code I wrote and abandondend before, the solution is really quite simple:
In a nutshell, don’t let yourself be seduced by high level abstractions and fancy language or stdlib features.
(Too) high level abstractions never work long time (I’ve seen too high-level code become unmaintainable over time much more often than too low-level code). Abstractions will eventually become a restriction when the changing environment no longer fits those abstractions (and that point will ineviatably come). At some point you’ll end up fighting your own abstractions which you were very proud of and which might have worked very well at the start of the project, and it will be hard to throw them away (both mentally and simply because it’s a lot of work to rewrite all the code into different abstractions - those two things are probably the biggest motivation killers).
Don’t “think in objects”, instead think in “data that needs to be processed”. To describe the shape of data, all you need is structs and arrays. To process the data all you need is functions. Inside functions all you need is sequences, conditions and loops.
Group your data types and functions into systems/modules, each with a ‘flat’ C-style interface. If you like the idea of classes, at least don’t expose them in the system interface, only use them for the internal implementation. Try to reduce dependencies between systems (especially shared data types, these will become a liability when one system needs to change).
When building a system, start to define its interface first, write hypothetical example code against that interface. Only when you’re happy with that, start to build the implementation. The system interface should be a clear separation line between “private inside and public outside”. Don’t entangle public and internal data types too much, keep them as separate as possible, even when that means that data that is passed over the ‘interface boundary’ needs to be copied. Never expose data types in the public interface that are heavily used in the internal implementation.
Take your time to read and debug-step through your code from time to time, this helps to stay familiar with the code and keep the design and implementation warts fresh in your head. Your subconscious will eventually come up with a clean solution.
That’s about the gist of it I think of how to build long-lived software with minimal maintenance overhead 
TL;DR: structs and functions in “flat” system interfaces is all you need.