Seeking advice regarding inline namespaces
Dear community, I am seeking advice regarding the usage of inline namespaces for header-only metaprogramming libraries. I was at Eric Niebler's talk on C++11 library design back in 2014, and I understand the problem that inline namespaces solve. I also read all of this past thread [1] on the mailing list, but did not find what I was looking for. I also understand how they can be useful for a normal header-only library __that would provide guarantees about ABI compatibility__. Indeed, if the ABI of the library changes from v1 to v2, then any client C1 exposing types from v1 in its interface and trying to link with a client C2 built against v2 will get a clean link-time error instead of a runtime error. Now, my question has several aspects: (1) How can you guarantee ABI compatibility from one version of a library to the next? IIUC, ABI can be broken by merely changing the size or the layout of a type, so guaranteeing backwards ABI compatibility means freezing those things in time, right? (2) Assuming that it is unreasonable for a library L to guarantee ABI compatibility from one version to the next, would there still be a reason to use inline namespaces to version that library? (3) When using inline namespaces to version a library (at all, not necessarily for header only libraries), are there other guidelines to follow? For example, I see that Eric Niebler's range-v3 library is all stuffed in the range/v3 subdirectory. IIUC, this would allow an eventual range-v4 library to cohabit with the range-v3 library, because people explicitly state which version of the library they're including. Is this the reason behind the v3 subdirectory? Any answer or pointer to answers is appreciated. Regards, Louis [1]: http://boost.2283326.n4.nabble.com/type-traits-general-Best-practice-for-inl... -- View this message in context: http://boost.2283326.n4.nabble.com/Seeking-advice-regarding-inline-namespace... Sent from the Boost - Dev mailing list archive at Nabble.com.
On 12/11/2015 06:26, Louis Dionne wrote:
(1) How can you guarantee ABI compatibility from one version of a library to the next? IIUC, ABI can be broken by merely changing the size or the layout of a type, so guaranteeing backwards ABI compatibility means freezing those things in time, right?
It's really hard to do this absolutely (since compiler settings can affect things as well), which is why it's rarely done. (But nice where possible.)
(2) Assuming that it is unreasonable for a library L to guarantee ABI compatibility from one version to the next, would there still be a reason to use inline namespaces to version that library?
Yes, this is the best reason to do so. Imagine a static library A that has been built against version 1 of L, and then an application that is trying to consume version 2 of L and the prebuilt library A. Without version-specific namespaces, there is a very good chance that this will successfully link, but then fail in some unspecified (and probably horrible, or worse: subtle) way at runtime. With version-specific namespaces, this will fail to link until the user either switches the application to use v1 of L or rebuilds A to use v2 as well. The above assumes that v2 of L does not define the namespace that v1 used. If instead v2 defined "ABI compatible symbols" in a v1-named namespace as well (which is harder), then the above scenario *might* link and even work correctly, assuming that either the app and lib A did not pass lib L's objects to each other, or that lib L defined ways to make them interoperable (which can also be hard).
On 12/11/2015 06:26, Louis Dionne wrote:
[...] (2) Assuming that it is unreasonable for a library L to guarantee ABI compatibility from one version to the next, would there still be a reason to use inline namespaces to version that library?
Yes, this is the best reason to do so. Imagine a static library A that has been built against version 1 of L, and then an application that is trying to consume version 2 of L and the prebuilt library A.
Without version-specific namespaces, there is a very good chance that this will successfully link, but then fail in some unspecified (and probably horrible, or worse: subtle) way at runtime. With version-specific namespaces, this will fail to link until the user either switches the application to use v1 of L or rebuilds A to use v2 as well.
The above assumes that v2 of L does not define the namespace that v1 used. If instead v2 defined "ABI compatible symbols" in a v1-named namespace as well (which is harder), then the above scenario *might* link and even work correctly, assuming that either the app and lib A did not pass lib L's objects to each other, or that lib L defined ways to make them interoperable (which can also be hard).
Ok, but since L can't guarantee ABI compatibility at all between versions, this means that such a version namespace must change between each version to ensure that you get a linker error even between v1.0.0 and v1.0.1, correct? In other words, what you want is Version 1.0.0: namespace L { inline namespace v1_0_0 { } } Version 1.0.1: namespace L { inline namespace v1_0_1 { } } Version 1.0.2: namespace L { inline namespace v1_0_2 { } } ... and so on, for literally each released version of the library L. Is this correct? Regards, Louis -- View this message in context: http://boost.2283326.n4.nabble.com/Seeking-advice-regarding-inline-namespace... Sent from the Boost - Dev mailing list archive at Nabble.com.
On 13/11/2015 01:24, Louis Dionne wrote:
Ok, but since L can't guarantee ABI compatibility at all between versions, this means that such a version namespace must change between each version to ensure that you get a linker error even between v1.0.0 and v1.0.1, correct? In other words, what you want is
Version 1.0.0:
namespace L { inline namespace v1_0_0 { } }
Version 1.0.1:
namespace L { inline namespace v1_0_1 { } }
Version 1.0.2:
namespace L { inline namespace v1_0_2 { } }
... and so on, for literally each released version of the library L.
Is this correct?
Yes, if you want to provide that feature to your users, then that's how to do it. Typically you'd have a macro or the namespace decls in some prefix header so you don't have to repeat yourself too much. The easiest thing to do is to provide a common version namespace for everything, but you could instead decide to version each type individually and increment them only when actually modified. There are other things you can do to reduce potential ABI breakage, such as using #pragma pack. This is less important for most consumers, though, as most apps don't play with the packing. There are also other compiler settings you can take into account in your namespace name when you know that they affect ABI, eg. whether exceptions are enabled or not. I can't think of a specific example at the moment but I know at least one Boost library does this.
On 13/11/2015 01:24, Louis Dionne wrote:
Ok, but since L can't guarantee ABI compatibility at all between versions, this means that such a version namespace must change between each version to ensure that you get a linker error even between v1.0.0 and v1.0.1, correct? In other words, what you want is
Version 1.0.0:
namespace L { inline namespace v1_0_0 { } }
Version 1.0.1:
namespace L { inline namespace v1_0_1 { } }
Version 1.0.2:
namespace L { inline namespace v1_0_2 { } }
... and so on, for literally each released version of the library L.
Is this correct?
Yes, if you want to provide that feature to your users, then that's how to do it. Typically you'd have a macro or the namespace decls in some prefix header so you don't have to repeat yourself too much.
The easiest thing to do is to provide a common version namespace for everything, but you could instead decide to version each type individually and increment them only when actually modified.
There are other things you can do to reduce potential ABI breakage, such as using #pragma pack. This is less important for most consumers, though, as most apps don't play with the packing.
There are also other compiler settings you can take into account in your namespace name when you know that they affect ABI, eg. whether exceptions are enabled or not. I can't think of a specific example at the moment but I know at least one Boost library does this.
Ok, thanks a lot for the information. Keeping this in mind, I'll consider whether the added complexity of versionning Hana's namespace seems to be worth it (given the nature of Hana as a metaprogramming library). Regards, Louis -- View this message in context: http://boost.2283326.n4.nabble.com/Seeking-advice-regarding-inline-namespace... Sent from the Boost - Dev mailing list archive at Nabble.com.
On 13/11/2015 14:26, Louis Dionne wrote:
Ok, thanks a lot for the information. Keeping this in mind, I'll consider whether the added complexity of versionning Hana's namespace seems to be worth it (given the nature of Hana as a metaprogramming library).
For pure compile-time constructs, probably not. Most of the benefit is in detecting incompatibilities within runtime types.
On 13/11/2015 14:26, Louis Dionne wrote:
Ok, thanks a lot for the information. Keeping this in mind, I'll consider whether the added complexity of versionning Hana's namespace seems to be worth it (given the nature of Hana as a metaprogramming library).
For pure compile-time constructs, probably not. Most of the benefit is in detecting incompatibilities within runtime types.
Well, it's a bit more subtle for Hana, because we can have both runtime and compile-time sequences. For example, it could happen that one uses a `hana::tuple` as a parameter of a function at an ABI boundary. If I decide to change the layout of `hana::tuple`, which is highly plausible for different reasons, I could break that client. I'll have to consider this. Regards, Louis -- View this message in context: http://boost.2283326.n4.nabble.com/Seeking-advice-regarding-inline-namespace... Sent from the Boost - Dev mailing list archive at Nabble.com.
participants (2)
-
Gavin Lambert
-
Louis Dionne