问题描述
示例:
namespace X{
inline namespace Y{
template<typename T>
struct A{
};
}
}
namespace X{
template<typename Z>
A(std::vector<Z>) -> A<Z>;
}
这会导致Clang 11发生编译错误,提示“推导指南必须在与模板X::Y::A
相同的范围内声明”
类似于模板专门化,推导指南也应在与类模板相同的语义范围内声明。 那为什么我为什么要专门对内联命名空间之外的类模板进行操作,而对于推导指南却不能呢?
特别是,这会引起另一个问题:
template<typename T>
struct Q{
operator std::vector<T>() {
return {};
}
};
namespace std{
template<typename T>
vector(Q<T>) -> vector<T>;
}
如果我想定义一个转换为std::vector
的类模板并为其声明一个推导指南,则编译器拒绝。在这种情况下(对于libc ++),我必须在namespace std::__1
中声明它。
CPP标准中是否有解决方案或解释?
解决方法
那为什么我可以在内联名称空间之外专门化类模板,但是对于演绎指南却不能呢?
因为您可以专门化模板。根据C ++标准[namespace.def]/7:
内联名称空间的成员可以在大多数方面使用,就像它们是封闭名称空间的成员一样。具体来说,将内联名称空间及其封闭的名称空间都添加到在依赖于参数的查找中使用的关联名称空间的集合中,无论何时,并且将命名内联名称空间的using指令隐式插入到封闭的名称空间中,例如未命名的命名空间。 此外,内联名称空间的每个成员随后都可以被部分专用,显式实例化或显式专用,就像它是封闭名称空间的成员一样
对于推导指南,它必须与类模板在相同范围中。根据标准[temp.deduct.guide]/3:
[...]推导指南应在与相应类模板相同的范围内声明,并且对于成员类模板,应具有相同的访问权限。 [...]
解决方案是显式赋予X::Y
范围:
namespace X::inline Y{
template<typename Z>
A(std::vector<Z>) -> A<Z>;
}
,
模板专业化的目的是,即使您不是模板的 author ,也可以向模板添加专业化。可能这样做是因为他们是该专业使用的类型的作者。出于这个原因,C ++标准库的规则禁止将声明添加到std
名称空间 except 中以进行模板专门化。
推导指南与模板专业化不同。它们被视为类模板定义的一部分,非常类似于构造函数和其他成员函数。这样,它们通常由类的创建者编写,通常紧随模板类的定义之后。考虑到这些期望,推论指南不存在于模板类定义本身的范围之外的范围内是没有意义的。
基本上,您不能仅仅将推论指南添加到其他人的班级模板中。
first version of the CTAD proposal及其每个派生版本的重点是将构造函数参数映射到类模板参数。最初被称为“演绎指南”的最初讨论为“规范工厂功能”。但是其周围的文字特别能说明问题:
我们建议使用一种表示法,以允许构造函数通过显式声明该类之外任何其他需要的构造函数推论的签名来指定其模板参数
请注意文本如何集中在“构造函数”上。这些规范工厂函数是构造函数和模板参数之间的映射。至少在概念上,它们被认为是某种构造函数。毕竟,隐式指南是从构造函数生成的,因此可以理解为显式指南在概念上等同于类构造函数。
的确,为什么需要显式演绎指南(即为什么不能完全依赖隐式指南)的原型示例集中在类型的构造函数上。即vector
的迭代器构造函数:
template<typename Iter>
vector(Iter first,Iter last);
访问此构造函数需要推论指南,因为Iter
显然没有映射到vector<T,A>
的模板参数。
最重要的是:显式推导是围绕类的构造函数构建的(尽管这些构造函数不必存在)。它们的存在是为了将构造函数参数类型映射到类模板参数。如果您不能从类的定义之外将构造函数添加到类中,那么可以说您也不能从类的定义之外添加显式推导指南。
显然,明确的指南是在模板类的定义之外编写的,但原理是相同的:指南是类的接口的一部分。
通过operator Typename
进行的隐式转换不会向Typename
添加构造函数。它可能允许Typename(other_type)
工作,但是就语言标准而言,这是复制/移动到Typename
中。它不是在修改Typename
的定义。