Hi Joe,
Thanks for the clear explanation. You've hit upon some genuine challenges. I can share some thoughts but not as satisfying an answer as you might hope for.
nimble itself has the same challenge. As far as I know, there is no way to distribute compiled objects in a package that can be linked against reliably and for each operating system. And I believe some of the reasons for that have to do with safety and security (on some operating systems, you can't routinely move a compiled library and then link against it).
nimble's (also non-ideal) solution combines a custom build process and a one-time compilation that happens in each session. The custom build process copies some of the files from inst/CppCode to src, where they can be compiled into nimble.so (or .dll on windows). That allows a user to load the package and call for example some of nimble's own distributions, just like for any package's compiled components from src. Then on the first call to compileNimble in a session, nimble builds a library to link against for that and any further calls to compileNimble in the session. That library uses some of the same inst/CppCode files and more. That one-time compilation in each session is the reason the first call to compileNimble always takes longer. Creating a custom build is not fully necessary for this approach; you could instead have two copies of some C++ code, one in src and one in inst.
A hard part is that as a package user of nimble, you don't have control to trigger a step like the one-time compilation when compileNimble is called (unless your package provides a wrapper for calling it), and you note it would not be great to do it on package loading. There may be some clever way to use a lazy object (via delayedAssign) to accomplish this, if you can arrange for an object that is touched only when a user needs one of your compiled pieces.
Another potential solution would be to look into R_GetCCallable. I believe this is what Rcpp arranges to use from the Rcpp::interfaces attribute in C++, which allows one package to use another package's compiled functions. This does not use linking but rather R's mechanism for sharing function pointers across packages.
I'm hoping some of the big changes coming in "nimble 2.0" will make this easier, but that won't help you right away.
HTH
Perry