
Pavol Droba wrote:
[...] I have put some info into the docs, but I'm not sure if it is correct and sufficient. Could some of you, that had lead the discussion about this topic (namely David Abrahams or David B. Held) check it out and tell me your opinion? [...]
Looks fine to me. However, instead of saying that the library makes assumptions about the exception safety of template and function arguments, I would say that providing the basic guarantee is a precondition for using the library. Also, I think it would be helpful to have a table of exactly which functions give a stronger guarantee, unless to_lower_copy() is the only such function, in which case you should just mention that in the original note. Note that it is operations which give exception safety guarantees, not types. For a given type, there may be functions (member or free) which offer the basic, strong or nothrow guarantee. Technically, you want to look at which member/user-defined functions of the argument types get called, and require those to provide the basic guarantee. Really, all functions should provide the basic guarantee, so this should really go without saying, but it's better to say it to remind users. This is probably how I would word that section: Exception Safety The library requires that all operations on types used as template or function arguments provide the basic guarantee. In turn, all functions and algorithms in this library, except where stated otherwise, will provide the basic guarantee. Now, here is where it gets tricky. You mention "const operations", which should probably read "pure operations", possibly with a note specifying that "pure" means "without side effects". But really, in order to provide the strong guarantee, you need to state exactly which user functions need to be pure. Let's analyze to_lower_copy() to see what we should say about it: template< typename ContainerT > inline ContainerT to_lower_copy( const ContainerT& Input, const std::locale& Loc=std::locale() ) { ContainerT Output; std::transform( string_algo::begin(Input), string_algo::end(Input), std::back_inserter<ContainerT>( Output ), string_algo::detail::to_lowerF(Loc) ); return Output; } Now, let's look at what functions are being called. First, std::locale() is called, but I'm pretty sure we can assume that does not modify global state. Thus, it is a pure function. Next, ContainerT() is called. If it provides the basic guarantee but modifies global state before throwing, then this function will not provide the strong guarantee. Next, string_algo::begin() and ::end() are called, which I assume are pure functions. Since std::back_inserter() is called on local data, it is pure with respect to to_lower_copy(). Since Loc is passed by const&, we assume that string_algo::detail::to_lowerF(Loc) does not modify Loc nor global state. Thus, it too is pure w.r.t. to_lower_copy(). Finally, since std::transform() is called on local or const data, it too is pure w.r.t. to_lower_copy(). So this function can give the strong guarantee if it has the signature { pure, strong, pure* } Where the first one is std::locale(), the second is ContainerT(), and the rest is std::transform et al. So what we really mean is that to_lower_copy() gives the strong guarantee when ContainerT's default constructor gives the strong guarantee. Note that none of the other c'tors of ContainerT need give any special guarantees. So be careful not to overspecify your client requirements for exception safety. Exception safety is a property of operations, not types. Hope that helps. Dave