Doc No: SC22/WG21/N1526 J16/03-0109 Date: September 18, 2002 Project: JTC1.22.32 Reply to: Benjamin Kosnik, Red Hat, 3333 N. Lincoln Ave #2, Chicago, IL, 60657 Email: bkoz@redhat.com Proposal to add namespace references to C++ This paper discussed problems arising in library implementations that provide different versions (optimized, debug, normative) of underlying types to the same abstract library specification. It identifies inadequacies of current namespace semantics to properly help solve those issues. Furthermore, it suggests some extensions to namespace facilities to overcome those difficulties. In summary, this proposal aims at supporting system programming and library implementations. 1. The problem. The ability to switch between similar objects defined in different namespace entities at compile time, while preserving separate link semantics, is desired, but not currently possible. More specifically, the fundamental issue may be described by the following sub-issues: (1) At any time, in a given translation unit, one wants to pretend that a set of names that are defined in a namespace N, are also members of a namespace M, for all purpose but linkage (i.e. name mangling). (2) Assuming (1), one wants to pretend that any member added to namespace N, will automatically be viewed as member of namespace M. (3) Furthermore, one wants to retain usual lookup rules, in particular argument dependent lookup, member definition rules, etc. (4) Names added to namespace M should NOT be considered members of namespace N. Any of the above issues will be referred to as Issue(1), Issue(2), Issue(3) and Issue(4) respectively. They will be illustrated in the remaining of this section. For instance: namespace std { // In this translation unit, use some magic syntax // to tie debug::vector to std::vector } std::vector vi; // OK, uses debug::vector. class udt { /* ... */ }; namespace std { // allow notation std::vector in explicit/partial specializations. template<> class vector { public: void bar() { } }; } Allowing this kind of construct would let library implementors make their offerings more useful and robust, with optimization, debug, and normative namespaces and associated types. Attempts to create this kind of library within the existing boundaries of the C++ language have failed, as detailed below. 1.1 Issues with using declarations. Perhaps the most obvious way of creating a C++ library with the desired behavior would be to use an existing namespace feature, using declarations. However, using-declarations do not work well as illustrated by the following example. namespace std { using debug::vector; // make std::vector an alias for debug::vector template void swap(T&, T&); } // OK, uses debug::vector. std::vector vi; std::vector wi; swap(vi, wi); // ERROR: does not find std::swap. namespace std { template<> struct vector { // ERROR: specialization not allowed // using std::vector. // ... }; } This approach falls down with user specialization, which is not allowed in the current language. Therefore, while the using-declaration mechanism solves Issue (4) and seems to approximate a solution to Issue(1), it does not solve it completely; furthermore Issues(2) and Issues(3) are not addressed. 1.2 Issues with using directives and namespace composition. The core issue presented in this paper seems to be closed to the scope of the programming technique named "namespace composition" as described in TC++PL3/§8.2.8. That technique relies on using-directive semantics. In a similar vein, specialization of types is not possible if using directives are used to compose a std namespace from a debug namespace, like so: namespace std { // pretend names from debug can prefixed with std:: using namespace std; template void swap(T&, T&); } std::vector v; // OK, resolves to debug::vector std::vector w; swap(v, w); // ERROR: does not find std::swap namespace std { template<> struct vector { // ERROR: specialization not allowed // using std::vector. // ... }; } As a conclusion, namespace composition solves Issue(2), Issue(4), and approximate Issue(1) but does not solve it completely. Furthermore it does not address Issue(3). 1.3 Issues with namespace aliases. Obviously, namespace-aliases solve Issue(1), Issue(2), Issue(3). However re-opening a namespace alias is not allowed. See example below. namespace debug { template class vector { public: void foo() { } }; } namespace std = debug; void foo() { using namespace std; // OK, uses debug::vector. vector vi; } namespace std { // ERROR: cannot use a namespace-alias name in a namespace extension. struct udt { }; template<> class vector { public: void bar() { } }; } For this functionality to be used in C++ libraries, this would have to be allowed. Would the issue be completely resolved if namespace-aliases were allowed to be re-open? In addition, if namespace aliases were allowed to be re-opened, then another question must be asked, and that is: how would the specialization for std::vector be mangled, as above? For instantiations of templates, this issue is straightforward: it uses the namespace of the definition that is being instantiated. For specialization, Would it be mangled with a std:: namespace or a debug:: namespace? Both are problematic. If the specialization is mangled as std::vector then other translation units, perhaps compiled with other entities such as optimized::vector in use as std::vector, will all mangle down to the same symbol. In effect this would continue to allow only one specialization of a template for a given type, regardless of namespace. Is this desired? Or will this have to change? Yes this is desired, because: (1) In a given translation unit, the user is not always aware that specializing in std:: will resolve to something different. (2) Any definition in std:: should be consistently identified across translation units even if debug:: and optimized:: were mixed in the whole program. A straightforward way to resolve that is to mangle any definition with std:: part. On the other hand, if this specialization is mangled as debug::vector, other translation units, which may have used an alternate vector, say optimized::vector would also be able to explicitly specialize. Would this count as a separate specialization? In effect, the namespace would become another template parameter. We are assuming the answer is yes. In addition, it would no longer be possible to link declared but not defined specializations regardless of the underlying namespace for std::vector. (Such as non-inline member functions of a template class specialization.) This limits the usefulness of this extension for library implementors would would like to implement both debug and normative versions of standard library types, and have other users of this library be able to provide standard-compliant specializations that are able to link with both debug and normative modes without recompilation. ie: // foo.h namespace std { struct udt { }; template<> swap(udt&, udt&); } // foo.cc #include "foo.h" namespace std { template<> void swap(udt& a, udt& b) { /* ... */ } } If a library uses foo.h, and the specialization of swap is allowed to mangle to things other than std, then the definition intended may not be found and used. Perhaps we are asking too much here... 1.4 Issues with template aliases. After looking at the existing namespace functionalities, the template alias proposal (N1449) was also considered. Its semantics does not cover the core issues presented in this paper. namespace optimize { template class vector { }; } namespace debug { template class vector { public: void foo() { } }; } std::vector v; // OK, resolves to debug::vector namespace std { template<> struct vector { // ERROR: a template-alias may not be // specialized. // ... }; } Question: Will allowing a template-alias specialization would resolve the issue? No, it would not because it does not solve Issue(2), Issue(3) and Issue(4). Errors with things like std::sort. 2. Proposed solution. We suggest a language support for namespace accretion that displays the property listed in Issue(1), Issue(2), Issue(3) and Issue(4). Another name for this idea might be "namespace composition," however this is already the name for a programming technique described in TC++PL3. 7.3.4 Namespace accretion syntax -1- Add a notion of namespace-accretion that has the properties listed in Issue(1), Issue(2), Issue(3) and Issue(4). namespace-accretion namespace namespace-name |= namespace-name; For instance: namespace std { } // Etablish an initial unambiguous namespace. namespace std |= debug; // Accrete std with debug. (this might be easy to implement) or: namespace std : debug { } // inheritances-style syntax For all purposes, except for linkage, a namespace composition establishes the properties listed in the beginning of sections 1. Example: namespace std { } namespace normative { template class vector { }; } namespace optimize { template class vector { }; } namespace debug { template class vector { public: void foo() { } }; } namespace std |= debug; std::vector v; // Uses debug::vector. Where debug::vector::foo mangled as _ZN5debug6vectorIiE3fooEv 2.1 Specialization As above, struct udt { }; namespace std { // 1 explicit specialization of class template<> class vector { public: void foo() { } }; } Where std::vector::foo mangled as _ZNSt6vectorI3udtE3fooEv And, assuming the immediate bit above is not seen: struct udt { }; namespace std { // 2 explicitly specialization of class member function template<> void vector::resize(size_type sz) { // do something special } } Where std::vector::foo mangled as _ZN5debug6vectorI3udtSaIS0_EE6resizeIEv (?) (Not sure about this one. Perhaps it should just mangle as if defined in std::, like above.) Acknowledgment: J. Merrill, F. Glassborrow, A. Stokes, B. Stroustrup., G. Dos Reis. In addition, Doug Gregor implemented much of this functionality with a different approach. His comments can be found as part of: http://gcc.gnu.org/ml/libstdc++/2003-08/msg00177.html