C++17中有关类模板参数推导的问题

4

我正在努力理解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<T>&) [with T=int]。这意味着我们最终会调用S<int>::S(const std::vector<int>&),一切都很好。

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

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<std::list<int>>::S(const std::vector<int>&) 的呼叫。但實際上我們得到了什麼?這個在哪裡被指定了呢?
基本上,我不直觀地理解 P0091r3 中 "模板名稱所指定的類別模板" 的含義:這是否意味著主模板,還是包括所有部分特化和明確完全特化呢?
(我也不理解 P0091r3 對 §7.1.6.2p2 的更改如何不破壞使用注入類名(如)的代碼。
template<class T>
struct iterator {
    iterator& operator++(int) {
        iterator result = *this;  // injected-class-name or placeholder?
        //...
    }
};

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

在任何现有版本的Clang或GCC中是否支持类模板推导和显式推导指南(可能在-f标志下,例如-fconcepts)? 如果是这样,我可以在实际情况下尝试一些示例,并可能解决一半的困惑。


截至目前,没有主流编译器(可能甚至没有一个编译器)支持构造函数的模板参数推导。如果我没记错的话,可能有一个Clang的分支在某个地方被用来进行实现经验的积累,但我甚至不确定它是否在任何在线平台上可用。 - Morwenn
2个回答

2
这个建议略有遗漏,但我认为意图是只考虑主类模板的构造函数。证据是新的[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的类模板。


重新注入类名:我看到了那个句子,但仍然无法弄清它是指“如果模板名称注入类名称,则程序无效”,还是“后跟开括号的模板名称永远不会被解释为注入类名称”,或者其他什么。实际上,你的解释(“只要可能,模板名称应被解释为注入类名称”)正好与我的相反!:P - Quuxplusone
@Quuxplusone [temp.local]/1 枚举了当注入类名被视为 模板名称 时的情况。 - T.C.

1
我认为目前P0091的措辞在这方面是不够明确的。它需要明确是否仅包括主类模板还是所有特化的构造函数。
话虽如此,我相信P0091的意图是部分特化不参与参数推导。该功能是为了让编译器决定类的模板参数。然而,选择部分特化的方法是实际使用的模板参数。要得到S>特化,就需要在S的模板参数列表中使用std::list。
如果您想使特定参数使用特定的特化,请使用推导指南。毕竟,这就是它们的用途。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接