A header-only library cannot *link* to anything, or it is not header-only.
A header-only library B can depend on symbols being provided by the
calling program P, which can delegate the task to library A, or to
another library C which provides the same interface as A. There should
be no direct connection from B to A. Depending on the design of B, this
may involve #including a header from A before #including a header from
B, or it can involve making the headers from A available to B so that B
can #include them directly. Where these symbols and/or headers come
from is the business of P, not of B.
--
Rainer Deyke (rai...@eldwood.com)
CMake defines targets for header-only libraries, which can have
dependencies on other targets, including on static or shared libraries.
So the program that "links" to the header-only library picks up its
dependencies recursively. As a result, the linker is invoked with all
(binary) libraries in the dependency tree.
Whether you can consider such library header-only in the first place is
a philosophical question. I do, because otherwise any library that calls
the standard library cannot be called header-only.
> A header-only library B can depend on symbols being provided by the
> calling program P, which can delegate the task to library A, or to
> another library C which provides the same interface as A. There should
> be no direct connection from B to A. Depending on the design of B, this
> may involve #including a header from A before #including a header from
> B, or it can involve making the headers from A available to B so that B
> can #include them directly. Where these symbols and/or headers come
> from is the business of P, not of B.
Reverse dependencies are rather uncommon, and it's not what is being
discussed here. The question is how the header-only library selects the
targets to depend on.
I think, it should not select any specific target unless it absolutely
must (i.e. it won't work otherwise). In terms of the earlier example,
the library B should depend on the generic target A. Now, that target A
can be defined as a header-only, shared or static variant, depending on
the user's choice or some default.
FWIW, the most common biting issue is when mixing static/shared.
All-static and all-shared are usually pretty safe.
But when libraries A and B both use dependency C, and either of A and B
is shared, then C must be shared as well -- things will often break
horribly if C is static.
(Having said that, what actually happens is highly dependent on whether
using Windows or something UNIXy, and for the latter what symbol
visibility level is being used. And for some libraries and some
platforms/settings then there will be no apparent problems.)
But this sort of thing is usually why tools like CMake prefer to offer
global settings to configure static/shared rather than letting it be
configured on a target-by-target basis. You *can* override that, but it
is at your own peril. (Or that of the downstream app.)
If this functionality works the way I think does, then I think it should
not be used.
> Whether you can consider such library header-only in the first place is
> a philosophical question. I do, because otherwise any library that calls
> the standard library cannot be called header-only.
Actually, the standard library is a great example of what I am talking
about. A header-only library (B) should not care about the specific
implementation of the standard library (A) being used by the program
(P). There is no link dependency from B to A. There is a requirement
from B to P that P must provide /a/ implementation of the C++ standard
library, but B doesn't care if it is libstdc++ or libc++ or even a
custom standard library implementation that is part of P and not a
separate library at all.
Again, to make my point absolutely clear: header-only library B "using"
library A does not imply a dependency from B to A. Instead, it implies
a requirement on a program P that uses B to also link to A. Header-only
libraries use interfaces. Programs link to implementations.
--
Rainer Deyke (rai...@eldwood.com)
And in what way do you think it works? (Honest question.)
>
>> Whether you can consider such library header-only in the first place
>> is a philosophical question. I do, because otherwise any library that
>> calls the standard library cannot be called header-only.
>
> Actually, the standard library is a great example of what I am talking
> about. A header-only library (B) should not care about the specific
> implementation of the standard library (A) being used by the program
> (P). There is no link dependency from B to A. There is a requirement
> from B to P that P must provide /a/ implementation of the C++ standard
> library, but B doesn't care if it is libstdc++ or libc++ or even a
> custom standard library implementation that is part of P and not a
> separate library at all.
That works for the standard-library, because you can be sure that one is
available. (Otherwise your C++ compiler would be broken.)
However, for other libraries A there is no way to guarantee that.
Of course, you need to make sure that (any) dependency A is available on
your build machine, otherwise building could never succeed. But the
extra burden, to prepare the build-files (aka CMakeLists.txt) of your
program P to find and explicitly configure even indirect dependencies
(using `find_package`, `target_link_libraries`,
`target_include_directories` etc.) is not to be underestimated.
For a complex dependency-hierarchy this can become a nightmare, in
particular if you need to find out in what order to process the
individual build-files (`add_subdirectories`, `find_package` etc.).
Instead, program P should only be expected to declare its direct
dependencies (using `target_link_libraries`) and automatically get all
the usage-requirements (aka dependencies) of these. This only requires,
that every dependency (CMake target) carries its own usage-requirements.
And, IMO, this should be true for header-only CMake targets, too.
>
> Again, to make my point absolutely clear: header-only library B
> "using" library A does not imply a dependency from B to A. Instead, it
> implies a requirement on a program P that uses B to also link to A.
> Header-only libraries use interfaces. Programs link to implementations.
>
I just want to emphasize that even such interfaces are part of the
usage-requirements I was talking about.
Therefore B has a **usage-dependency** on A. And the CMake target for B
should communicate this (through its usage-requirements) to its user,
the program P (or more specific its CMake target).
The usage-requirements of the CMake target for B will become the
build-requirements of the CMake target for P (and could possibly even
become its usage-requirements as well).
And whether A is header-only or not is irrelevant. This (indirect)
dependency from P to A can either mean linking against A or just having
the correct include-search-path for the headers of A.
The CMakeLists.txt file for building P needs to have this information
either way. And this can be provided explicitly (as you prefer) which
can become quite complex and tedious, or automatically and indirectly
through B.
Deniz
PS: Maybe watching the first 11 minutes of my CMake talk [1] from
Meeting C++ 2018 might help to better understand this target-centric
approach CMake took.
[1] https://youtu.be/y7ndUhdQuU8
--
BENOCS GmbH
Dipl.-Inform. Deniz Bahadir
Reuchlinstr. 10 D
10553 Berlin
Germany
Phone: +49 - 30 / 577 0004-22
Email: deniz....@benocs.com
www.benocs.com
Board of Management: Stephan Schroeder, Dr.-Ing. Ingmar Poese
Commercial Register: Amtsgericht Bonn HRB 19378
I think it forces the users of library B to (indirectly) link to library
A, as opposed any other library that implements the interface of A. In
the worst case, it might even unnecessarily force a specific version of A.
>> Actually, the standard library is a great example of what I am talking
>> about. A header-only library (B) should not care about the specific
>> implementation of the standard library (A) being used by the program
>> (P). There is no link dependency from B to A. There is a requirement
>> from B to P that P must provide /a/ implementation of the C++ standard
>> library, but B doesn't care if it is libstdc++ or libc++ or even a
>> custom standard library implementation that is part of P and not a
>> separate library at all.
>
> That works for the standard-library, because you can be sure that one is
> available. (Otherwise your C++ compiler would be broken.)
> However, for other libraries A there is no way to guarantee that.
You guarantee it by documenting it as a requirement for the users of B.
The human users, not CMake.
> Of course, you need to make sure that (any) dependency A is available on
> your build machine, otherwise building could never succeed. But the
> extra burden, to prepare the build-files (aka CMakeLists.txt) of your
> program P to find and explicitly configure even indirect dependencies
> (using `find_package`, `target_link_libraries`,
> `target_include_directories` etc.) is not to be underestimated.
> For a complex dependency-hierarchy this can become a nightmare, in
> particular if you need to find out in what order to process the
> individual build-files (`add_subdirectories`, `find_package` etc.).
In order to build P, you need to build the specific versions of all of
the libraries that P requires, with the specific build settings that P
requires. The build process should never pick up random library
versions found in system directories. Program P must be able to say
"use this modified version of libpng instead of the upstream version",
"use LibreSSL instead of OpenSSL", and "don't use zlib at all, I'll
provide my own version of the zlib functions".
--
Rainer Deyke (rai...@eldwood.com)
If library B is a good citizen it uses `find_package` to look for its
dependencies (more specifically, for the CMake target of its
dependencies) and provides customization points (CMake options or simple
variables).
So if Program P wants to force a specific version of the indirect
dependency A upon B it can set these customization points beforehand or
it can just call `find_package` for that indirect dependency explicitly
beforehand. The later call to `find_package` from B's CMakeLists.txt
should then just return the same target (without even bothering searching).
Of course, this requires careful crafting of B's CMakeLists.txt. (But it
really can be worth it.)
In case the indirect dependency A is header-only itself, it becomes even
simpler because you do not have to cope with a library that might have
been compiled using different/incompatible compiler-flags.
IMO, CMake is quite powerful and one can achieve some really "funky stuff".
But, as always, one should remember to not make it too complex or one
will be unable to find anyone else who is willing to maintain it later.
Deniz
--
BENOCS GmbH
Dipl.-Inform. Deniz Bahadir
Reuchlinstr. 10 D
10553 Berlin
Germany
Phone: +49 - 30 / 577 0004-22
Email: deniz....@benocs.com
www.benocs.com
Board of Management: Stephan Schroeder, Dr.-Ing. Ingmar Poese
Commercial Register: Amtsgericht Bonn HRB 19378
It does, however, need to be sure that it is using the same standard
library as P (and with the same settings). Mixing multiple standard
libraries into the same P will only end poorly -- which is usually also
true (but less immediately obvious) when mixing multiple variants of
other single libraries into P.
You're assuming that by not specifying an explicit link from B to the
standard library that it will inherently have the same settings as P.
You're even mostly correct, because the standard library has a bit of
special handling in the build tools.
But this issue is not unique to the standard library, but applies to any
dependency, including ones that you do have to explicitly tell the build
tools about.
No, I'm saying that by not having a separate compilation step, there is
literally no way for B to have different settings than P, because there
is nothing that could be compiled with different settings. This is true
regardless of the build system, because it is true at the compile level.
There is no set of arguments that the build system can pass to the
compiler that would cause B to be built with different settings than P.
B is a header-only library. To use B, the C++ preprocessor literally
pastes the code from B into P. Therefore, B /has/ no build settings.
There are only the build settings from P, which are applied
indiscriminately to P's own code and the code from B that P #includes.
--
Rainer Deyke (rai...@eldwood.com)
That's actually completely false. The preprocessor itself *is* build
settings -- defines can be defined differently (along with other
compiler options) between the two builds.
And yes, there may still be two builds. There is the build of P itself
(which includes A) and the build of another library C that is linked
into P but also includes A. Both of these can be via B or separately,
and both can cause different code for B to be generated if the settings
differ, which is an ODR violation.
(Or cause two distinct copies of B to be generated, where C is a shared
library with private visibility, for example.)
For some examples, there's usually a compiler option to choose whether
the standard library is linked as static or as shared, which in turn
causes different #defines to be defined that will affect compilation.
Another example is things that can vary due to the C++ language
selected, or whether or not you've defined various "posix extension"
flags, or library-specific configuration defines. Or something as
simple as compiling one with exception support and one without.
Having a header-only library is not a silver bullet to avoid these kinds
of compilation incompatibilities. In fact it makes some of them worse.
There are no two builds in my example. There is only P. Yes, using B
modifies the build settings of P, but B has no build settings of its own.
By the same token, linking P to separately compiled library A also
modifies the build settings of P, but this is separate from the build
settings that are actually used to /build/ A.
> And yes, there may still be two builds. There is the build of P itself
> (which includes A) and the build of another library C that is linked
> into P but also includes A. Both of these can be via B or separately,
> and both can cause different code for B to be generated if the settings
> differ, which is an ODR violation.
That's a different situation. If you add a separately compiled library
C to the equation, then C does have its own build settings, which need
to be compatible with P's build settings. This is true whether C uses B
or not, and whether B is header-only or not.
Or you could, if C allows it, use C as a header-only library, which
completely solves the problem.
--
Rainer Deyke (rai...@eldwood.com)