应该通过类模板特化推导来考虑推导指导参数初始化吗?

Oli*_*liv 10 c++ language-lawyer template-argument-deduction c++17

作为这个问题的后续,我测试了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
 }
Run Code Online (Sandbox Code Playgroud)

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

初始化和重载解析按照[dcl.init]和[over.match.ctor],[over.match.copy]或[over.match.list](适用于所执行的初始化类型)中的描述执行假设类类型的对象,其中所选择的函数和函数模板被认为是该类类型的构造函数,用于形成过载集,[...]

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

Bar*_*rry 6

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);
}
Run Code Online (Sandbox Code Playgroud)

但是我们实际上从来没有调用我们用于演绎的合成函数和函数模板.我们只是执行重载决策并选择最佳候选者 - 我们只用它来选择类类型,然后我们重新开始.我们在任何时候都不应该要求复制.

我认为这是一个gcc bug.提起86439.

  • @Oliv - 根据[\ [[dcl.fct.def.delete \]/2](https://timsong-cpp.github.io/cppwp/),`decltype`的行为是正确的(因为C++ 11) n4659/dcl.fct.def.delete#2).我想这也是GCC在这里发出错误的原因.就像我说的,我不知道.但我认为巴里的论证是一个很好的理由,因为它不应该*是一个错误. (2认同)