类模板专用化演绎是否应该考虑演绎指南参数初始化?

Should deduction guide argument initialization considered by class template specialization deduction?

本文关键字:演绎 参数 初始化 专用 是否      更新时间:2023-10-16

作为这个问题的后续,我测试了clang和gcc的行为。看起来这两个编译器对 c++ 标准有不同的解释。

在下面的示例中,GCC 拒绝编译,如果需要根据演绎指南假设构造函数参数复制不可复制的参数。Clang 不执行此检查:

#include <cstddef>
struct not_copyable{
not_copyable()=default;
not_copyable(const not_copyable&)=delete;
};
struct movable{
movable()=default;
movable(movable&&);
};
template <typename T, size_t N>
struct A
{ template <typename ... Ts> A (Ts const & ...) {} };
template <typename T, size_t N>
struct B
{ template <typename ... Ts> B (const Ts & ...) {} };
template <typename T, typename ... Ts>
A(T const &, Ts const & ...) -> A<T, 1U + sizeof...(Ts)>;
template <typename T, typename ... Ts>
B(T, Ts ...) -> B<T, 1 + sizeof...(Ts)>;

int main()
{
not_copyable nc;
movable m;
auto a0 = A{nc};    // gcc & clang -> compile
auto a1 = A{m};     // gcc & clang -> compile
auto b0 = B{nc};    // clang ->compile;  gcc -> error
auto b1 = B{m};     // clang ->compile;  gcc -> error
}

在思考中,正确的行为在C++标准[over.match.class.deduct]/2的这一段中定义:

初始化和重载解析按中所述执行 [dcl.init] 和 [over.match.ctor], [over.match.copy],或 [over.match.list](根据初始化类型而适用( 执行(对于假设类类型的对象,其中 选定的函数和函数模板被视为 用于形成重载的此类类型的构造函数 设置,[...]

我强调">为了形成过载集",因为我认为这是 clang 和 gcc 分歧的地方。Clang似乎没有检查演绎指南假设构造函数是否可行,但gcc确实如此。哪个编译器是正确的?

Clang 似乎没有检查演绎指南假设构造函数是否可行,但 gcc 确实如此。

实际上,演绎指南是一个可行的功能。一个可行的函数只意味着参数的数量匹配,约束得到满足,你可以为每个参数/参数对形成隐式转换序列。当我们检查 ICS 是否存在时,[over.best.ics]/2:

其他属性(如生存期、存储类、对齐方式、参数的可访问性、参数是否为位字段以及是否删除函数(将被忽略。

删除函数不会使其不可行,这一点非常重要,因为它最终仍然可以成为最佳可行候选者。这意味着删除not_copyable的副本构造函数的事实应该只有在我们实际调用它时才生效。

例如,gcc 和 clang 都拒绝此程序。#1是一个可行的候选项,并且它是最佳可行的候选项,尽管删除了复制构造函数:

struct NC {
NC() = default;
NC(NC const&) = delete;
NC& operator=(NC const&) = delete;
};       
void foo(NC );                            // #1
template <typename T> void foo(T const&); // #2
int main() {
NC nc;
foo(nc);
}

但我们从未真正调用用于演绎的合成函数和函数模板。我们只是执行重载解析并选择最佳候选项——我们只用它来选择类类型,然后我们重新开始。在任何时候,我们实际上都不应该要求复制。

我认为这是一个 gcc 错误。提交 86439。