On 26/06/2019 20:26, Andrey Semashev wrote:
Most of the time this Just Works™;
I'd say, most of the time it doesn't work. Including any foreign headers, including standard library headers, in lib.hpp will break the program at some stage, either compilation or linking.
True, although there are workarounds for that; the simplest being that if you already #included everything that the library header does outside of the namespace (except for those things that you do want inside the namespace), then include guards will usually prevent the standard headers being included inside the namespace. (assert.h being a notably annoying exception, as that traditionally lacks guards for some inexplicable reason.) Of course, this also means that you need to defeat the guards of the library itself -- but this typically happens for free if it uses #pragma once guards rather than explicit #defines. And macro definitions in the library can be a problem as well. So you're right, it's not universally applicable, though it can be very useful in specific circumstances. I don't know whether modules will make this scenario better or worse. :)
I'm not saying I like specialization as a way of customization though.
There's exactly two places where a specialization of C<T> can be written: immediately below C, or immediately below T. Anywhere else can give you extreme grief. Of course, ADL isn't much better in that regard, except that you don't have to mess with namespaces as much. (Although actually calling ADL functions correctly can be troublesome in its own way.)
But what if the function has two output parameters? Memory is allocated for both and returned as a raw pointer by the function. One of the out_ptrs is then destroyed and calls reset(), and this throws. The other out_ptr is then destroyed as well, and now it has a problem; it either has to call reset() as well and possibly throw yet again, or leak memory.
Good point. In that case I see no good solution, given that on error there may be garbage in the out pointers.
To clarify, I meant that the function itself succeeds (and so does not return garbage), but then cleanup of the function arguments is where the exception is thrown. Especially as this occurs in unspecified order. A similar problem can occur if construction of one of the function arguments fails -- if the out_ptr hasn't been constructed yet, then it won't be destructed either and there's no change in behaviour. If it has, then it will be destructed and will try to reseat its raw pointer into a smart pointer -- which will only work correctly for out_ptr if it always initialises its raw pointer to nullptr explicitly, as mentioned below. For inout_ptr it's not a problem -- it just release()d a pointer and then reset() the same one right back again. If the exception occurs after the function call full-expression, then the raw pointers have been successfully saved into smart pointers, and there shouldn't be a problem any more. Similarly if it occurs before the call expression starts. If the function itself throws, it is its duty to avoid leaking memory (basic guarantee) and to not output deleted pointers (either by explicitly setting them to nullptr or leaving them at the same value as on input). Conversely, it's out_ptr's responsibility to always provide a nullptr as input. For inout_ptr, that responsibility falls to the caller to provide a valid possibly-not-nullptr pointer.