【问题标题】:Questions about class template argument deduction in C++17C++17类模板实参推导问题
【发布时间】:2016-09-07 07:39:50
【问题描述】:

我正在尝试理解P0091r3(当前 C++ 草案标准 N4606 中采用的“类模板的模板参数推导”论文)。

我相信我理解它在最简单的情况下是如何工作的,其中 template-name 标识单个模板:

template<class T>
struct S {
    S(T);
    S(const std::vector<T>&);
};

int main()
{
    std::vector<int> v;
    auto s = S(v);
}

S 标识主模板,因此我们创建了一个虚构的重载集,其中包含

template<class T> void Sctor(T);
template<class T> void Sctor(const std::vector<T>&);

并对虚拟调用执行重载解析

Sctor(v)

确定在这种情况下我们要调用虚构的Sctor(const std::vector&lt;T&gt;&amp;) [with T=int]。这意味着我们最终会致电 S&lt;int&gt;::S(const std::vector&lt;int&gt;&amp;) 并且一切正常。

我不明白在存在部分专业化的情况下这应该如何工作。

template<class T>
struct S {
    S(T);
};

template<class T>
struct S<std::list<T>> {
    S(const std::vector<T>&);
};

int main()
{
    std::vector<int> v;
    auto s = S(v);
}

我们直观地想要的是对S&lt;std::list&lt;int&gt;&gt;::S(const std::vector&lt;int&gt;&amp;)的调用。然而,这就是我们真正得到的吗?这是在哪里指定的?

基本上我不能直观地理解 P0091r3 的“由 template-name 指定的类模板”是什么意思:这是指主模板,还是包括所有部分特化和显式完整还有专业吗?

(我也不明白 P0091r3 对 §7.1.6.2p2 的更改如何不使用 injected-class-name 破坏代码,例如

template<class T>
struct iterator {
    iterator& operator++(int) {
        iterator result = *this;  // injected-class-name or placeholder?
        //...
    }
};

但这完全是一个不同的问题。)


任何现存版本的 Clang 或 GCC 是否支持类模板推导和显式推导指南(可能在 -f 标志下,如 -fconcepts 是)?如果是这样,我可以在现实生活中尝试其中一些示例,并且可能会消除我一半的困惑。

【问题讨论】:

  • 到目前为止,没有主要编译器(可能根本没有编译器)支持构造函数的模板参数推导。如果我没记错的话,可能在某个地方有一个 Clang 分支,用于收集实施经验,但我什至不确定它是否可以在线获得。

标签: c++ templates language-lawyer template-meta-programming c++17


【解决方案1】:

这在提案中有点被忽略了,但我认为其意图是只考虑主类模板的构造函数。这方面的证据是新的 [class.template.deduction] 具有:

  • 对于由模板名称指定的类模板的每个构造函数,具有以下属性的函数模板是候选者:[...]

如果我们谈论的是“the”类模板,那么这是主要的类模板,尤其是在名称查找时找不到类模板的部分特化 ([temp.class.spec]/6)。这也是原型实现(见下文)的表现。

在论文中,类模板部分特化在“隐式演绎指南的优缺点”一节中进行了考虑,但出于对主类模板中的构造函数可能触发硬(非 SFINAE)错误的担忧:

template<class T> struct X {
   using ty = T::type;
   static auto foo() { return typename T::type{} };
   X(ty); #1
   X(decltype(foo())); #2
   X(T);
};
template<class T>
struct X<T*> { 
   X(...);
};
X x{(int *)0};

您要求考虑类模板部分特化构造函数的请求表面上是合理的,但请注意,这可能会导致歧义:

template<class T> struct Y { Y(T*); };
template<class T> struct Y<T*> { Y(T*); };
Y y{(int*) 0};

可能需要通过类模板的专门化对隐式生成的演绎指南进行排名(作为决胜局)。

如果您想尝试原型实现,作者已在 github 上发布了他们的 clang 分支:https://github.com/faisalv/clang/tree/clang-ctor-deduction


论文中的讨论(“关于注入类名的注释”)表明注入类名优先于模板名;添加了措辞以确保这一点:

template-name 应该命名一个不是 injected-class-name 的类模板。

【讨论】:

  • Re injection-class-names:我看到了那句话,但仍然无法弄清楚它是否意味着“如果 template-nameinjected-类名,程序格式错误,”或“一个模板名后跟一个左括号永远不会被解释为一个注入的类名”或什么。 ...事实上,您的解释(“template-name 应尽可能解释为 injected-class-name”)与我的完全相反! :P
  • @Quuxplusone [temp.local]/1 枚举注入的类名何时被视为 模板名
【解决方案2】:

我想说的是,目前 P0091 的措辞在这方面没有得到充分说明。它确实需要明确它是只是主类模板还是它是否包含所有特化的构造函数。

话虽如此,我相信 P0091 的 意图 是偏特化不参与论证推导。该功能是允许编译器决定类的模板参数是什么。但是,选择部分特化的是那些模板参数实际上是什么。获得S&lt;std::list&lt;T&gt;&gt; 特化的方法是在S 的模板参数列表中使用std::list

如果你想让一个特定的参数使用一个特定的特化,你应该使用一个推导指南。毕竟,这就是他们的目的。

【讨论】:

    猜你喜欢
    • 2017-12-30
    • 1970-01-01
    • 2019-12-25
    • 1970-01-01
    • 1970-01-01
    • 2017-06-09
    • 1970-01-01
    • 2021-04-17
    • 2018-02-15
    相关资源
    最近更新 更多