以下内容基于 bolov 的回答。
这就是我组织代码的方式 (live example):
// declarations
template<typename T>
struct Foo_temp;
namespace A {
struct tag;
using Foo = Foo_temp<tag>;
}
namespace B {
struct tag;
using Foo = Foo_temp<tag>;
}
namespace C {
struct tag;
using Foo = Foo_temp<tag>;
}
// definitions
template<>
struct Foo_temp<A::tag> { /*...*/ };
template<>
struct Foo_temp<B::tag> { /*...*/ };
template<>
struct Foo_temp<C::tag> { /*...*/ };
// testing
template<typename T>
struct is_Foo : public std::false_type {};
template<typename T>
struct is_Foo<Foo_temp<T>> : public std::true_type {};
这样,基于部分专业化的所有Foo 只有一个测试。当然,您可能会说,通过将声明与定义等分开,这比为每个命名空间专门化 is_Foo 需要更多的代码。但是,如果要定义的类型特征不仅仅是 is_Foo(比如从每个 Foo 中提取信息,或者对每个 Foo 应用类型转换),这种设计最终会得到回报(对我来说),因为对于每个特征有一个定义。
与Foo 之间的其他合作形式(如继承公共基数或声明特殊成员)相比,我发现这对编译器来说更轻量级。
需要注意的是 Foo_test 特化需要在最初声明 Foo_test 的地方定义,即外部命名空间 A,B,C。因此,如果这些定义需要使用来自这些名称空间的信息,情况会更加复杂。在这种情况下,您可以求助于
namespace A {
struct tag;
struct Foo_impl { /*...*/ };
using Foo = Foo_temp<tag>;
}
//...
template<>
struct Foo_temp<A::tag> : A::Foo_impl {};
在每个命名空间中都有实际的定义。
标签?
您现在可以清楚地看到Foo_temp 只是将tag 与实现(Foo_impl)相关联。这些标签实际上将命名空间转换为类型,以用作模板参数,因此您正在手动建模该语言不直接支持的东西。您可以将相同的标签用于其他类似的类定义,例如Bar。
事实上,如果您重新考虑您的应用程序,您可能会发现最终它只是您所追求的标签,而不是名称空间(但也许我错了)。我大量使用这种没有命名空间的设计:我有一个命名空间tag,我在其中定义了所有标签,所以我可以说例如
template<typename T>
struct array<tag::dense, T> { /*...*/ };
template<typename T>
struct array<tag::sparse, T> { /*...*/ };
定义密集或稀疏数组表示,同时能够使用单个定义测试is_array。以here 为例。您甚至可以将标签视为 properties 并以特定顺序的组合或顺序使用它们,或者将它们制作为模板并添加参数(请参阅here)。标签可以根据上下文具有不同的含义,例如想象tag::lazy 定义一个数组、一个元组或一个函数。