I've implemented an enum <-> string conversion library using Boost.Preprocessor. In a header file it declares a few templates, e.g. template<typename T> inline const std::string to_string(const T& v) = delete;
, then any time the macro is used it defines an enum class
and an explicit (full) template specialization, e.g. enum class MyEnum {...}; template<> inline const std::string to_string<MyEnum>(const MyEnum& v) {...};
The problem is that this only works as long as the macro is used in the same namespace as the primary template declarations.
From this answer, in c++11 it is possible to define a full specialization outside the namespace of the primary template declarations as long as it's an enclosing namespace. However, this is a problem since the idea is to be able to use the library in any arbitrary scope.
An explicit specialization shall be declared in a namespace enclosing the specialized template. An explicit specialization whose declarator-id is not qualified shall be declared in the nearest enclosing namespace of the template, or, if the namespace is inline (7.3.1), any namespace from its enclosing namespace set.
I thought maybe I could potentially make the declarations part of the macro, in an inline namespace, but I haven't figured out a way to ensure that they are only included once per namespace if the macro is used multiple times - otherwise I get an "ambiguous template specialization" error.
Each member of an inline namespace can be partially specialized , explicitly instantiated or explicitly specialized as if it were a member of the enclosing namespace.
Which brings me to my question, of whether I can bring in the template names (defined in namespace whose parent is the global namespace) with using-declarations, and then define explicit specializations. Something like this:
namespace a {
template<typename T>
inline const std::string some_func(T) = delete;
}
inline namespace b {
using a::some_func;
template<> inline const std::string some_func(int v) { return "some_func(int)"; }
template<> inline const std::string some_func(char v) { return "some_func(char)"; }
}
If I try to compile that with g++ I get (it compiles and works as expected if I use -fpermissive
):
error: explicit specialization of ‘template<class T> const string a::some_func(T)’ outside its namespace must use a nested-name-specifier [-fpermissive]
13 | template<> inline const std::string some_func(int v) { return "some_func(int)"; }
With clang:
error: no function template matches function template specialization 'some_func'
template<> inline const std::string some_func(int v) { return "some_func(int)"; }
^
If I use its nested-name-specifier template<> inline const std::string a::some_func(int v) {...}
, then with g++ I get:
error: declaration of ‘const string some_func(int)’ not in a namespace surrounding ‘a’
13 | template<> inline const std::string a::some_func(int v) { return "some_func(int)"; }
| ^
error: specialization of ‘template<class T> const string a::some_func(T)’ in different namespace [-fpermissive]
note: from definition of ‘template<class T> const string a::some_func(T)’
7 | inline const std::string some_func(T) = delete;
| ^~~~~~~~~
With clang:
error: cannot define or redeclare 'some_func' here because namespace 'b' does not enclose namespace 'a'
template<> inline const std::string a::some_func(int v) { return "some_func(int)"; }
~~~^
I don't quite understand why doing this is not permissive, from using-declarations:
Names introduced into a namespace scope by a using-declaration can be used just like any other names, including qualified lookup from other scopes.
From the notes here I see that (below), but I'm bringing in the primary template name, not a specialization.
A using-declaration cannot name a member template specialization (template-id is not permitted by the grammar):
struct B { template<class T> void f(); };
struct D : B {
using B::f; // OK: names a template
// using B::f<int>; // Error: names a template specialization
void g() { f<int>(); }
};
I'm not too sure I fully understand template-id, but I don't think that's the issue here since the example actually brings in a template name.
Sorry for the long question, and if anyone knows of a different solution to my problem that would also be very welcome :)