clang和 gcc在防止按值拷贝构造函数方面是不是都过于谨慎?还是说它们正确地遵循了一个过于谨慎的标准?

编程语言 2026-07-09

请看下面这段代码:

#include <concepts>

template <typename T>
class A {
    A(A<void>) requires (!std::same_as<void, T>){}
};

最新版本的clang与 gcc会拒绝它,二者都指向拷贝构造函数必须通过引用传递参数这一点(参考 godbolt)。

来自clang的确切错误信息:

<source>:5:14: error: copy constructor must pass its first argument by reference
    5 |     A(A<void>) requires (!std::same_as<void, T>){}
      |              ^
      |               const &
<source>:5:14: note: in instantiation of template class 'A<void>' requested here
1 error generated.
Compiler returned: 1

以及来自gcc的错误信息:

<source>: In instantiation of 'class A<void>':
<source>:5:49:   required from here
    5 |     A(A<void>) requires (!std::same_as<void, T>){}
      |                                                 ^
<source>:5:5: error: invalid constructor; you probably meant 'A<void> (const A<void>&)'
    5 |     A(A<void>) requires (!std::same_as<void, T>){}
      |     ^
Compiler returned: 1

然而:从签名看,只有当类模板参数 T 偶然等于 void 时,它才是一个拷贝构造函数,并且 requires-子句在这种情况下故意禁止实例化该构造函数。

让我们看看 相关的标准段落

A declaration of a constructor for a class X is ill-formed if its first parameter is of type cv X and either there are no other parameters or else all other parameters have default arguments. A member function template is never instantiated to produce such a constructor signature.

我不太确定如何解读这一点:

  • 这个声明是否已经“不合法”,因为我们必须在考虑 requires-子句之前就拒绝这个签名?在我看来,这样的标准会显得过于谨慎……
  • 还是编译器过于谨慎,标准会允许上面的代码片段?但为什么两个编译器在这里会达成一致?

解决方案

ill-formed 在标准中是一个非常强烈的用语;它用于非常根本性的错误(以及“不合法、无需诊断的信息”这样的错误)。

实际上,用标准措辞来说,你正在做的大致等同于:

A(decltype(auto) x) requires false {}

占位类型 decltype(auto) 使程序不合法,即使构造函数并不存在。

标准在这里可能过于严格了。但就算你有一个 requires false(或等价物),你仍然是在声明,而该声明按照标准的写法不能是“不合法的”。

据我所知,要对这点进行修改相当棘手。一般情况下,判断一个requires-clause在给定模板实例化下是否可能返回true(或始终为false)几乎是不可能的。这里当然很简单;但标准必须决定在无法实现时该怎么办,而要求编译器去完成一项不可能的任务,也不算是写标准的好方法。

一个可能的“修正”是把这一要求推迟到重载分辨时间;如果在重载分辨时间发现了前述形式的构造函数,那么该构造函数就变成不合法?但那样就不太理想,我们希望在使用该类之前就进行检查。

我们也可以设想一次伪查找,类似于 X( std::declval<X>() ),并声明如果该查找在其可能的重载集合中包含一个形式为 X(X) 的构造函数,那么该构造函数就是不合法的?我不确定在“简单”情形下是否完全等同,并且在“病态”情形下是否足够,但看起来是可能的。

你现在不能把模板参数传给构造函数(至少在当前阶段如此),而我们要阻止的情形是为了拷贝构造而必须进行拷贝构造(!),所以对这一情形的检测或许就足以实现语言所要阻止的目标。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章