Shader class in OpenGL
Is it really necessary to split vertex and fragment shaders into their own type? It causes the type to go from 4 bytes to 16 bytes on a typical 64 bit processor, just for it to store a vtable ptr for the destructor that's never going to do anything different. 

They could privately inherit and not have a virtual destructor, but I would rather they just be a single class and let incorrectly assigning shaders to a program be a runtime error. There's already a potential failure point when creating the shader program, and the gl error should alert the client to what went wrong, so it's not introducing a new point of failure.

You can't use the default move functions because they won't zero-out the id, and glDeleteShader will be called twice on the same id.

Consider using a factory function to create shaders from file instead of doing file IO in the constructor. It will make it easier to test, and it's also not obvious that this is a blocking function that shouldn't be called on UI threads. 

It also makes it more portable to possibly use in a context that doesn't want to use fstreams for doing file IO, for example if you had zipped shaders or hosted them on a cloud it would make more sense to pass in a byte array which is what  glShaderSource will expect.

If you're going to use a path constructor, consider std::filesystem::path.