为啥标准不将模板构造函数视为复制构造函数?
Posted
技术标签:
【中文标题】为啥标准不将模板构造函数视为复制构造函数?【英文标题】:Why doesn't the standard consider a template constructor as a copy constructor?为什么标准不将模板构造函数视为复制构造函数? 【发布时间】:2019-09-14 16:27:57 【问题描述】:这里是复制构造函数的定义,[class.copy.ctor/1]:
类 X 的非模板构造函数是复制构造函数,如果它的第一个参数是类型 X&、const X&、volatile X& 或 const volatile X&,并且要么没有其他参数,要么所有其他参数都有默认参数 ( [dcl.fct.default])。
为什么标准排除模板作为复制构造函数?
在这个简单的例子中,两个构造函数都是拷贝构造函数:
struct Foo
Foo(const Foo &); // copy constructor
Foo(Foo &); // copy constructor
;
看这个类似的例子:
struct Foo
Foo() = default;
template <typename T>
Foo(T &)
printf("here\n");
;
int main()
Foo a;
Foo b = a;
在本例中,here
将被打印。所以看起来我的模板构造函数是一个复制构造函数,至少它的行为像一个(它是在通常调用复制构造函数的上下文中调用的)。
为什么文中有“非模板”的要求?
【问题讨论】:
注意:我注意到Foo b = a
实例化并调用Foo::Foo<Foo>(Foo&)
。它可能宁愿调用隐式声明的复制构造函数。
如果你 Foo c = std::move(a);
会发生什么?
@Caleth 不错的测试:coliru.stacked-crooked.com/a/cff8c0b034585446 没有打印任何内容(gcc 中继)
“复制构造函数”具有特殊状态。它不仅仅是任何可用于复制的旧构造函数。
@sebrockm:标准规定了拷贝构造函数是什么。它定义了,如果构造函数是非模板的,并且具有特定的参数,那么它是一个复制构造函数。所有其他构造函数都不是复制构造函数。
【参考方案1】:
让我们先暂时搁置模板。如果一个类没有声明复制构造函数,则会生成一个隐式默认的构造函数。它可能被定义为删除,但它仍然是默认的。
成员模板不是成员函数。成员仅在需要时从它实例化。
那么编译器如何仅从类定义中知道是否需要使用T = Foo
进行特化呢?它不能。但这正是它需要决定如何处理隐式默认复制构造函数(AND 移动构造函数)的潜在需求的基础。这变得一团糟。
最简单的方法是排除模板。无论如何,我们总会有一些复制构造函数,默认情况下它会做正确的事情TM,并且会受到重载决议的青睐,因为它不是从模板实例化的。
【讨论】:
那么您对 Q 中显示的程序中的 g++ 和 clang++ 打印“here”有何看法? 所以基本上你说这是因为规则:“如果类定义没有显式声明复制构造函数,则隐式声明非显式构造函数”。而且我们仍然期望定义通常的隐式复制构造函数,无论模板构造函数如何。是的,这似乎是解释。 @YSC - 老实说?对我来说感觉就像一个错误。我目前无法进行更多实验来对此发表更多评论。这是我手机上的。 @YSC- 或者......默认的有一个 const 限定参数,而模板没有。所以这是一个更好的匹配。T const &
还会发生这种情况吗?
@YSC:是打印出来的,因为传递的参数不是const,所以模板比隐式匹配更好。但是(对我来说)不将模板专业化称为复制构造函数是令人困惑的。但是标准这样做是因为 StoryTeller 的解释。至少,这是一个明智的解释【参考方案2】:
为什么文中有“非模板”的要求?
鉴于它是不同的,复制构造函数可以是模板。在存在复制构造函数模板的情况下,非复制构造函数怎么可能不模棱两可?考虑一下:
struct Foo
// ctor template: clearly useful and necessary
template <typename T>
Foo(const T&)
// copy ctor: same signature! can't work
template <typename T>
Foo(const T &)
;
此外,从不是Foo
的对象构造Foo
可以通过转换或普通构造来实现,但是允许从非Foo
对象进行复制构造会改变复制到复制,包括转换。但这已经可以用现有的方案(转换或非复制构造)来实现。
在本例中,here 将被打印。所以看来我的模板构造函数是一个拷贝构造函数
您展示的示例不调用复制构造,而是调用普通的隐式构造。如果将构造函数模板更改为
template <typename T>
Foo(const T &)
// ^^^^^
printf("here\n");
然后Foo b = a;
导致编译器生成的复制构造函数被调用。请注意,编译器生成的复制 ctor 具有以下签名:
Foo(const Foo&);
这需要在Foo b = a;
中将const
限定符添加到a
。您的 sn-p 中的原始构造函数模板 Foo(T&)
更匹配,因为没有添加 const
-qualifier。
【讨论】:
啊,是的!事实上,整个 const 与非常量的事情。 +1 我不明白你回答的第一部分。您是否打算将template
用于两个构造函数?我认为合乎逻辑的是,如果构造函数专用于T=Foo
,那么它就是一个复制构造函数。
template
都是有意的,我试图指出,如果你有一个普通的 ctor 模板(因为它很有用),但你还需要为你的类实现复制构造函数(因为它管理资源),它们不能共存,但它们也不能是一个实体,因为它们服务于不同的目的。诚然,我明白你的意思,但现实情况是你有一个通用的 ctor 模板,当用Foo
实例化时,它会做正确的事情?我需要专门化它,这不过是非模板复制ctor,对吧?
@lubgr:对不起,我的意思是“用T=Foo
实例化”(所以不是明确的专业化)。在阅读 SO 中的答案后,我最近对这些术语感到困惑。我想我需要阅读这方面的标准(我记得我们称之为实例化,标准称为隐式(已实例化)专业化)。【参考方案3】:
复制构造函数的形式为 X(X& ) 或 (X const&),如果您自己没有声明,编译器将为您提供它。如果您使用模板类,非模板可能会出现在这里。
假设有一个具有模板复制构造函数的模板类。问题是,当您使用该类的另一个具有相同模板类型的实例来实例化该类时,您的模板复制构造函数将不会被调用。
问题不在于您的复制构造函数模板不匹配。问题是隐式复制构造函数不是函数模板,在重载决议方面,非模板优先于模板特化。
来源:C++ template copy constructor on template class
【讨论】:
以上是关于为啥标准不将模板构造函数视为复制构造函数?的主要内容,如果未能解决你的问题,请参考以下文章
默认构造函数,为啥我的类似乎有三个?当编译器将类视为结构时?
为啥SqlParameter名称/值构造函数将0视为null?
为啥隐式复制构造函数调用基类复制构造函数而定义的复制构造函数不调用?
为啥我们需要复制构造函数以及何时应该在 java 中使用复制构造函数