SDL Initialization and Management wrappers
Throwing in a noexcept function

This is legal, see https://stackoverflow.com/questions/39763401/can-a-noexcept-function-still-call-a-function-that-throws-in-c17. However, you already have doubts about it, so that's telling you it's probably not a very proper thing to do. Compilers will also warn about this.

If you want your program to terminate when it can't create a window, I would make this explicit, and instead of using throw, call std::terminate() explicitly.

Define a namespace for your library

All your classes and structs have names with SDL prefixed, for good reasons. However, you can make it more explicit to the compiler that you want everything in a separate namespace. The advantage is that within that namespace, you don't have to use the prefix anymore. For example, you can write:

namespace SDL {
  class Window {
    friend class Context;
  public:
    ~Window() noexcept = default;
    ...
  };

  class Context {
  public:
    explicit Context(Opts const &amp;options) noexcept;
    ~Context() noexcept;

    Window Window() const noexcept;
    Renderer Renderer() const noexcept;

  private:
    Opts options_ {};
    SDL_Window *window_ {};
    SDL_Renderer *renderer_ {};
  };
}


In your application, you can instantiate an Window like so:

SDL::Window myWindow;


Avoid forward declarations whenever possible

Forward declarations should only be used if really necessary. Otherwise, they are just duplicating code, with the potential to make mistakes. If you are using a SDL_Window * in your header files, just make sure you #include &lt;SDL.h&gt; in it.

Avoid friend classes

Why is SDLContext a friend class of SDLWindow? If it needs to access some private member variables, maybe you should add public accessor functions instead? From the code you posted I don't see any reason why a friend class is necessary. In general, it is something to be avoided, since it bypasses the usual member protection scheme.

Use of std::experimental

While we all would like to have the latest and greatest language features available, consider that your code might be compiled by others using older compilers. Using experimental C++20 features might prevent them from using your code. Furthermore, observer_ptr&lt;&gt; is not doing anything really, it's just there as an annotation. It's there to convey to other users that this pointer is not owned. SO it is more useful to use this for the public API than for private member variables. But, that also brings me to:

Have classes own their own pointers to SDL2 objects

It looks a bit weird that SDLWindow has a private constructor that takes a pointer to an SDL_Window. Why not have SDLWindow call SDL_CreateWindow() itself in its constructor? The same goes for SDLRenderer.

When you do this, then SDLContext should no longer store pointers to SDL_Window and SDL_Renderer, but it should instead store a SDLWindow and SDLRenderer directly:

class Context {
  Window window_;
  Renderer renderer_;

public:
  Context(...);
  ...
};