C++ std::初始值设定项{x,y,z}(CTAD)有效吗?

C++ std::初始值设定项{x,y,z}(CTAD)有效吗?,c++,language-lawyer,c++17,C++,Language Lawyer,C++17,当显式构造std::initializer\U list时,是否可以推断模板参数(U)(例如,使用类模板参数推断(CTAD) 换句话说,我知道以下陈述是有效的: std::initializer_list<int> x1{1, 2, 3}; std::initializer_list<int> x2 = {1, 2, 3}; auto x3 = std::initializer_list<int>{1, 2, 3}; 编译器不同意是否可以推断std::in

当显式构造
std::initializer\U list
时,是否可以推断模板参数(
U
)(例如,使用类模板参数推断(CTAD)

换句话说,我知道以下陈述是有效的:

std::initializer_list<int> x1{1, 2, 3};
std::initializer_list<int> x2 = {1, 2, 3};
auto x3 = std::initializer_list<int>{1, 2, 3};

编译器不同意是否可以推断
std::initializer\u list
的模板参数:

#include <initializer_list>

struct s {
    s(std::initializer_list<int>);
};

void f() {
    std::initializer_list x1{1, 2, 3};         // Clang ERROR; GCC OK;    MSVC OK
    std::initializer_list x2 = {1, 2, 3};      // Clang ERROR; GCC OK;    MSVC OK
    auto x3 = std::initializer_list{1, 2, 3};  // Clang ERROR; GCC OK;    MSVC OK

    s x4(std::initializer_list{1, 2, 3});      // Clang ERROR; GCC ERROR; MSVC OK
    s x5{std::initializer_list{1, 2, 3}};      // Clang ERROR; GCC OK;    MSVC OK
    s x6 = s(std::initializer_list{1, 2, 3});  // Clang ERROR; GCC OK;    MSVC OK
    s x7 = s{std::initializer_list{1, 2, 3}};  // Clang ERROR; GCC OK;    MSVC OK
    s x8 = std::initializer_list{1, 2, 3};     // Clang ERROR; GCC OK;    MSVC OK

    void g(std::initializer_list<int>);
    g(std::initializer_list{1, 2, 3});         // Clang ERROR; GCC OK;    MSVC OK
}
#包括
结构{
s(标准:初始值设定项列表);
};
void f(){
std::initializer_list x1{1,2,3};//叮当声错误;GCC正常;MSVC正常
std::initializer_list x2={1,2,3};//叮当声错误;GCC正常;MSVC正常
auto x3=std::初始值设定项列表{1,2,3};//叮当声错误;GCC正常;MSVC正常
sx4(std::initializer_list{1,2,3});//铿锵错误;GCC错误;MSVC OK
sx5{std::initializer_list{1,2,3};//叮当声错误;GCC正常;MSVC正常
sX6=s(std::initializer_list{1,2,3});//叮当声错误;GCC正常;MSVC正常
sX7=s{std::initializer_list{1,2,3};//叮当声错误;GCC正常;MSVC正常
sX8=std::初始值设定项列表{1,2,3};//叮当声错误;GCC正常;MSVC正常
无效g(标准:初始值设定项列表);
g(std::initializer_list{1,2,3});//铿锵错误;GCC正常;MSVC正常
}
(请参见上的此示例。)

已测试的编译器:

  • 带有
    -std=c++17-stdlib=libc++
    -std=c++17-stdlib=libstdc++
  • 带有
    -std=c++17的GCC版本8.3
  • 带有
    /std:c++17的MSVC版本19.16

    • Clang是唯一正确的编译器。是的,真的

      当编译器看到没有模板参数的模板名称时,它必须查看模板的演绎指南,并将其应用于带括号的init列表中的参数<代码>初始值设定项\u列表
      没有任何明确的推导指南,因此它使用可用的构造函数

      初始值设定项列表
      拥有的唯一可公开访问的构造函数是其复制/移动构造函数及其默认构造函数。从带括号的init列表创建
      std::initializer\u列表
      不是通过可公开访问的构造函数完成的。它是通过编译完成的,这是一个仅限于编译器的过程。只有编译器才能执行


      鉴于所有这些,除非您是从现有列表复制,否则不可能在
      初始值设定项\u列表
      s上使用CTAD。最后一部分可能是其他编译器在某些情况下如何使其工作。在演绎方面,他们可能会将带括号的init列表演绎为一个
      初始值设定项\u列表
      本身,而不是作为一系列要应用的参数,因此演绎指南会看到一个复制操作。

      正如Nicol Bolas对永不固定答案的评论所总结的,这是一个巨大的错误。总之,作为任何类类型,
      std::initializer\u list
      都有编译器提供的演绎指南

      如上所述,从一个假设的构造函数C(C)派生出一个额外的函数模板,称为复制推断候选函数

      这意味着
      std::initializer\u list
      编译器隐式声明了此演绎指南:

      template <class T>
      initializer_list (initializer_list <T>) -> initializer_list <T>;
      
      模板
      初始值设定项列表(初始值设定项列表)->初始值设定项列表;
      
      当使用非空的initalizer list参数为该扣减指南扣减
      T
      时,将应用以下标准规则:

      如果从p中删除引用和cv限定符会为某些p′和N给出std::initializer_list或p′[N],并且参数是非空的initializer list([dcl.init.list]),则会对initializer list的每个元素独立执行演绎,将P′作为单独的函数模板参数类型P′i,并将第i个初始值设定项元素作为相应的参数

      因此,
      T
      应推导为
      int
      ,因此类模板参数推导应成功


      免责声明:我不是一个响亮的倡导者…

      你认为这应该被允许吗?因为我觉得这是疏忽,我不相信。您可以应用[over.match.class.decredit],其中包括从复制扣减候选中形成一个函数模板。该函数模板看起来像
      template auto X(std::initializer\u list)->std::initializer\u list
      然后[over.match.class.decrete]说使用提供的初始值设定项
      {1,2,3}
      将函数模板视为正在初始化的某个假设类类型的构造函数模板,转到[over.match.list]。如果你这样做,你就会推断出
      T=int
      @T.C.,你所说的是有意义的。如果我添加下面的演绎指南(应该与复制演绎候选者相同),Clang现在编译我的测试程序中的每一行(如MSVC):
      名称空间std{template initializer_list(std::initializer_list)->initializer_list;}
      @T.C.:“该函数模板看起来像模板auto X(std::initializer\u list)->std::initializer\u list;“如果这就是它应该如何工作的话,这就解释了为什么Clang会出错。这是一个initializer list构造函数,它在[over.match.list]中具有优先级但是,如果Clang的演绎实现不将其视为初始值设定项列表构造函数,或者如果其演绎实现不应用[over.match.list]的规则,我可能会发现它缺少该指南。它可能正在寻找一个3元素构造函数。
      template <class T>
      initializer_list (initializer_list <T>) -> initializer_list <T>;