Specialization and namespaces (original) (raw)
N3730
2013-08-28
Mike Spertus
mike_spertus@symantec.com
Specializations and namespaces
Overview
We propose to allow specializing templates from within a different namespace. The motivation is that when we declare a new class, it is natural to want to provide associated template specializations. For example, it is really painful that whenever I declare a class, I need to class all open namespaces and enter namespace std just to specializestd::hash as shown belownamespace A { namespace B {/* ... */ class C { /* ... */ }; } } namespace std { template<> struct hash<A::B::C> { size_t operator()(A::B::C const &c) { /* ... */ } }; } namespace A { /* Reenter namespace I am using */ namespace B { /* ... */ } }
Instead, I should be able to specialize std::hash contiguous with the rest of the definition of class C without having to break out of its namespace:namespace A { namespace B {/* ... */ class C { /* ... */ }; template<> struct ::std::hash<C> { std::size_t operator()(C const &c) { /* ... */ } };/* ... */ } }
The technical point is that the primary template identifies the template's namespace, so we don't need to use the namespace enclosing the specialization's definition to identify it's namespace.
We also propose (to be straw-polled separately) the use of friend to put specializations of external templates in a class, just like we allow external friend functions to be defined within a class:namespace A { namespace B {/* ... */ class C { /* ... */ template<> friend struct ::std::hash<C> { std::size_t operator()(C const &c) { /* ... */ } }; }; } }
We also discuss an alternative approach of allowing namespaces to be open in a non-containing namespace.
Motivation
The primary motivation is that the natural place to specialize templates for a class is often alongside the definition of the class.
- In the Overview, we illustrate this for std::hash.
- std::less provides another example of this.
- In in n3403, it is anticipated that the (not yet existent) std::prompt will often be explicitly specialized.
- Of course, this doesn't just apply for template classes in std. The same considerations apply to specializing boost::fusion::traits::tag_of in the Enabling Tag Dispatching examplein the Boost.Fusion documentation.
- Specializing test classes in a putative test framework could also benefit from something like this.
- Creating explicit specializations of current or future type traits that yet lack compiler intrinsics.
- As one other example that some (but not all) may find interesting, I have worked on a multi-million line code base that had a DEFINE_CLASS preprocessor macro to define classes according the the local coding standards. One of the things this macro did was to specialize an XXX::Guid struct for this class, so that each conforming class had a GUID. In part due to the need to exit and reenter the namespace in this macro, the entire codebase was placed in a single namespace (Yes, all of the namespace info could be put in the macro, but avoiding such boilerplate is a primary motivation for the macro in the first place)
- and so forth
Specializing in a different namespace
The rules for specializing in a different namespace are fairly straightforward.
The primary template is identified using normal lookup rules
For example, all of the following are OK. For clarity, the varying sections are green.namespace A { namespace B {/* ... */ class C { /* ... */ }; template<> struct ::std::hash<C> { std::size_t operator()(C const &c) { /* ... */ } };/* ... */ } }
namespace A { namespace B {/* ... */ class C { /* ... */ }; template<> struct std::hash<C> { std::size_t operator()(C const &c) { /* ... */ } };/* ... */ } }
using namespace std;namespace A { namespace B {/* ... */ class C { /* ... */ }; template<> struct hash<C> { std::size_t operator()(C const &c) { /* ... */ } };/* ... */ } }
The same rule applies to declarationsusing namespace std; namespace A { namespace B {/* ... */ class C { /* ... */ }; template<> struct hash<C>;/* ... */ } }
The current lexical scope is used
I propose using the current lexical scope both because it seems more natural to me, and is more compatible with the rules for defining friend members in §11.3p7. This consistency is particularly nice when we discuss declaring friend specializationsbelow.namespace A { namespace B {/* ... */ class C { /* ... */ }; template<> struct std::hash<C> { // OK std::size_t operator()(C const &c) { /* ... */ } }; struct less<A::B::C> { // Error: Not in namespace std. Also too awkward. bool operator()(C const &c) { /* ... */ } };/* ... */ } }
All explicit specializations are supported
While the illustrations above have all been fully specializing classes, there is no restriction on the type of specialization.template<class C, int i = 0> struct Foo { std::string foo() { return "foo" }; }; template<class C> struct Bar { std::string bar() { return "bar" }; } namespace A { namespace B {/* ... */ class C { /* ... */ }; template<int i> struct Foo<C, i> { // OK. Partial specialization /* ... */ }; template<> std::string Bar<C>::bar()) { // OK. ordinary method specialization return "Special bar"; };/* ... */ } }
friend specializations
Just as we allow external functions to be defined as friends (§11.3p6), we allow specializations to be defined as friends. There is one important difference, which is that the specialization is naturally placed in the namespace of the primary template, not in namespace scope (as for function definitions in §11.3p6).namespace A { // Repeated from Overview for easy reference namespace B {/* ... */ class C { /* ... */ template<> friend struct hash<C> { // Specializes std::hash std::size_t operator()(C const &c) { /* ... */ } }; }; } }
Note that this only allows specializations to be defined as friends. Support for declaring classes or primary templates is not required. (However, speaking personally, allowing declarations of friend classes seems worthwhile in conjunction with ADL for template parameters, but this is not required for this proposal...)
Additional functionality: Opening namespaces in non-containing namespaces
This section looks at allowing a namespace to be opened while in a non-containing namespacenamespace A { namespace B {/* ... */ class C { /* ... */ }; namespace ::std { template<> struct hash<A::B::C> { size_t operator()(A::B::C const &c) { /* ... */ } };/* ... */ } } }
This has the benefit of being more general than just allowing specialization in another namespace. For example, if we want to create a header that can be #includeed from within while within a namespace (e.g., to use multiple versions of the same API within a single program), we can write// Foo.hnamespace :: { // Make sure we are in global scope#include <string> }/* ... */
While this is nice, I am not recommending adding this functionality for the following reasons:
- It does not handle the primary motivating example of specializing as well as the approaches above. In particular, this approach doesn't encapsulate the definitions of the specializations with the definition of the class nearly as well.
- By operating in the lexical scope of the namespace containing the primary template, it is awkward to define the specialization as illustrated by the need to repeatedly use A::B::C in the first example in this section.
- To reopen global scope, you need to say "namespace :: {" as "namespace { already has a special meaning, potentially leading to errors:
// Foo.hnamespace { // Oops! Including <string> in unnamed namespace#include <string> }/* ... */
- Even if you are opening a non-global scope, the use of "::" is mandatory, which is also error-prone:
namespace A { namespace B {/* ... */ class C { /* ... */ }; namespace std { // Oops! Trying to specialize in A::B::std template<> struct hash<A::B::C> { size_t operator()(A::B::C const &c) { /* ... */ } };/* ... */ } } }
- Perhaps the inclusions from within a namespace use case could eventually be better handled by modules.