模板模板参数和转发引用

11
假设我们有以下代码:
template<typename T>
class C
{};

template <typename T, template <typename> class Container>
void dummyMe(Container<T>&&)
{};

int main(int argc, char* argv[])
{
    C<int> c;
    dummyMe(c);
    return 0;
}

由于第一个dummyMe参数是右值引用,所以无法编译。能否有人用标准语言解释一下为什么模板模板参数与转发引用不兼容?并在通俗易懂的英语中解释原因。
附:我看过thisthat问题,但在答案中没有看到任何真正的证明。
上面链接中的答案以及这个问题的答案都声称Container<T>不能被视为模板参数。我看不出为什么会这样。让我们把例子变得更简单:
template <template <typename=int> class Container>
void dummyMe(Container<>&&)
{};

现在我们有一个几乎与以下内容相同的示例:
template <typename Container>
void dummyMe(Container&&)
{};

但是它被以完全不同的方式处理。为什么?为什么Container<>&&不能被视为与template <typename=int> class ContainerContainer&&相同,就像typename Container一样?

1
你期望Container被推断为什么?template <typename> C&?这不是一个可以放置引用限定符的有效语句。 - yuri kilochek
@yurikilochek,我期望函数是dummyMe<int, C<int>&>(C<int>&),没有其他的。 - ixSci
@bolov,哪个?一个没有标准单引号的,还是一个带有不相关的单引号的? - ixSci
@bolov,它有一个标准的解释,但没有任何证据。我不明白为什么那个答案是正确的。 - ixSci
请每次只提一个问题。如果您有需要,请开启后续问题。 - StoryTeller - Unslander Monica
显示剩余4条评论
2个回答

12
术语“转发引用”在C++17 draft n4659的[temp.deduct.call/3]中有描述:
引用一个不代表类模板(在类模板参数推导期间)的cv非限定模板参数的右值引用被称为转发引用。
在你的例子中,Container<T>不是一个模板参数,它是由模板参数TContainer组成的类型。为了使引用真正具有转发功能,只能使用T&&。虽然Container是一个模板参数,但你不能引用一个模板(上面的段落甚至明确提到了这一点)。类型Container<T>与模板Container不同,它是一个实例化的类。1 虽然你可以使用SFINAE来获得只能绑定到容器类型的转发引用,但我个人认为最好还是重载函数。
template <typename T, template <typename> class Container>
void dummyMe(Container<T>&&)
{}

template <typename T, template <typename> class Container>
void dummyMe(Container<T>&)
{}

1 - 从类模板实例化出的类被称为实例化类。[temp.spec/2]


@ixSci - 这不是一个模板参数,而是由模板参数组成的类型。无论如何,您都不能引用模板,必须实例化它以获得一个类(一种类型)。 - StoryTeller - Unslander Monica
1
@ixSci - 你误读了标准。模板 Container 确实是一个参数。Container<T> 不是一个模板,而是一个类型。它也不是你定义的函数模板的参数。 - StoryTeller - Unslander Monica
我理解你的逻辑,但我仍然看不出你提供的引用如何证明任何事情。当函数被调用时,Container<T>会被推导出来。我们的Container是一个模板模板参数,它是一个可以并且应该接受另一个模板参数的模板。碰巧我们使用T作为它的参数。我不明白为什么它突然变成了一个不可推导的实体。实际上,它被推导为某种类型(例如在示例中是C<int>)。为什么它应该与简单的T&&有所不同? - ixSci
@ixSci Container 是一个模板参数,但这并不重要。我们正在谈论对 Container<T> 的引用,它绝对不是一个模板参数。 - n. m.
1
即使假设 - 反事实地 - Container<T>&& 是某种转发引用,这仍然无法帮助 OP。 转发引用“微调”的工作方式是将参数类型更改为 A&,然后针对该类型进行推断以产生 T:= A&。 但在那个反事实的世界中,微调意味着我们将根据 C<int>& 推导出 Container<T>,这将导致... 推导失败。 - T.C.
显示剩余10条评论

7
上面链接中的答案以及对这个问题的回答都声称Container<T>不能被视为模板参数。
什么是模板参数,什么不是,这个问题并没有太多的解释。在[temp.param]中已经明确定义了。
template-parameter: 
    type-parameter 
    parameter-declaration 
 type-parameter: 
    type-parameter-key ...(opt) identier (opt)
    type-parameter-key identier(opt) = type-id 
    template < template-parameter-list > type-parameter-key ...(opt) identier(opt)
    template < template-parameter-list > type-parameter-key identier(opt) = id-expression 
type-parameter-key: 
    class
    typename

从这些生产规则可以清晰地看出,dummyMe 恰好有两个模板参数:typename Ttemplate <typename> class Container。命名这些参数的标识符分别为 TContainer。其中 T 命名第一个参数,Container 命名第二个参数。而 Container<T> 不是标识符,也不代表这两者之一。

但是为什么?“Container”应该伴随着尖括号(<>)出现,所以我们给了“Container”这些括号。为什么它变成了一个不同的实体? - ixSci
我已经引用了标准中简单且无可争议的事实,正如问题所要求的那样。如果您想问为什么语言没有按照您喜欢的方式定义模板参数,请提出另一个问题。 - n. m.
“容器是应该伴随着尖括号(<>)的东西。”这当然不是真的。您可以将“Container”作为参数传递给进一步的模板(接受适当的模板模板参数),而无需使用尖括号。 - n. m.
终于搞定了。它真的不能是模板参数,因为语法不同。谢谢! - ixSci

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