使用std :: initializer_list参数的非成员函数(/非构造函数上下文)的重载解析

dfrib

(以下所有ISO标准参考均参考N4659:2017年3月Kona工作草案/ C ++ 17 DIS,并且所有示例程序结果在GCC和Clang上对于C ++ 11,C ++ 14和C ++ 17都是一致的

考虑以下示例:

#include <initializer_list>

// Denote as A.
void f(float) {}

// Denote as B.
void f(std::initializer_list<int>) {}

int main() {
    // Denote call as C1.  
    f(1.5F);    // Overload resolution picks A (trivial).

    // Denote call as C2.
    f({1.5F});  // Overload resolution picks B (and subsequently fails; narrowing).

    return 0;
}

这导致调用C2将重载B选为最佳可行函数,然后由于列表初始化范围变窄而失败(由[dcl.init.list] / 7控制):

error: type 'float' cannot be narrowed to 'int' in initializer list [-Wc++11-narrowing]

  • 为什么f({1.5F}); 函数调用被认为void f(std::initializer_list<int>)是唯一的最佳可行函数,其匹配度高于void f(float)Afaics这与[over.ics.list] / 4[over.ics.list] / 9冲突(请参阅下面的详细信息)。

我正在寻找有关相关标准段落的参考。

请注意,我知道有关列表初始化(构造函数重载)和std::initializer_list<>(以及有关此主题的各种SO问题)的重载解析的特殊规则,在[over.match.list] / 1的std::initializer_list<>控制下,强烈推荐使用这些规则但是,afaics在这里不适用(或者,如果我错了,至少可以说与[over.ics.list] / 4中显示的标准示例发生冲突)。


我自己的细节和调查

对于上述C1和C2调用,都按[over.call.func](根据[over.match] /2.1[over.match.call] / 1)中的指定应用重载解析,尤其是[over.call]。 func] / 3这两个调用候选函数为:

candidate_functions(C1) = { void f(float),
                            void f(std::initializer_list<int>) }

candidate_functions(C2) = { void f(float),
                            void f(std::initializer_list<int>) }

每个调用中参数列表与调用中的表达式列表相同

根据[over.match.viable] / 2[over.match.viable] / 3可行函数集与两个调用候选函数相同

viable_functions(C1) = { void f(float),
                         void f(std::initializer_list<int>) }

viable_functions(C2) = { void f(float),
                         void f(std::initializer_list<int>) }

最佳可行功能

简而言之,根据[over.match.best] / 1给定函数调用最佳可行函数可行的,这里特别是单参数调用(每个调用可行函数没有特殊情况)对于单个参数,具有从相应可行函数的单个参数到单个参数的最佳隐式转换序列的函数。

C1:最佳可行功能

该调用f(1.5F)具有身份标准转换(不转换),因此可以(平凡且)毫无疑问地选择AA完全匹配(按照[over.ics.scs] / 3)作为最佳可行函数。

best_viable_function(C1) = void f(float)

C2:最佳可行功能

Afaics按照[over.ics.list] / 1的规定,将呼叫的隐式转换序列排名f({1.5F})为可行候选者,由[over.ics.list]控制

当参数是初始化程序列表([dcl.init.list])时,它不是表达式,并且特殊规则适用于将其转换为参数类型。

将C2匹配到A

对于单个参数为基本float类型的A的匹配[over.ics.list] / 9尤其是[over.ics.list] /9.1适用[强调我的]:

否则,如果参数类型不是类:

  • (9.1)如果初始化列表中有一个本身不是初始化列表的元素,则隐式转换序列是将元素转换为参数类型所需的序列[ 示例

    void f(int);
    f( {'a'} );             // OK: same conversion as char to int
    f( {1.0} );             // error: narrowing
    

    — 结束示例 ]

  • [...]

这可以说是意味着对于呼叫匹配隐式转换序列f({1.5F}}f(float)作为相同的转换序列floatfloat; 即身份转换和随后的完全匹配。但是,按照上面的示例,由于调用C2甚至不会导致模棱两可的最佳可行函数,因此我的逻辑肯定存在一些缺陷。

使C2与B匹配

对于与单个参数[over.ics.list] / 4适用的B匹配[重点]:

否则,如果参数类型为,std​::​initializer_­list<X>并且初始化列表的所有元素都可以隐式转换为X则隐式转换序列是将列表的元素转换为所需的最差转换X,或者如果初始化列表没有元素,则标识转换。即使在调用initializer-list构造函数的上下文中,此转换也可以是用户定义的转换。[示例:

void f(std::initializer_list<int>);
f( {} );                // OK: f(initializer_­list<int>) identity conversion
f( {1,2,3} );           // OK: f(initializer_­list<int>) identity conversion
f( {'a','b'} );         // OK: f(initializer_­list<int>) integral promotion
f( {1.0} );             // error: narrowing

[...]

—结束示例]

因此,在此示例中,隐式转换序列是列表的单个元素转换为的最差转换,这是转换排序的标准转换序列([over.ics.scs] / 3),尤其是根据[conv.fpint] / 1floatint

最佳可行功能

根据我自己对上述标准段落的解释,最佳可行功能应与C1调用相同,

best_viable_function(C2) = void f(float) ?

但我显然缺少了一些东西。

dfrib

列表初始化序列:一种序列转换为时的特殊情况排序 std::initializer_list

[over.ics.rank] /3.1适用于这种情况,并且优先于[over.ics.rank] / 3 [强调我的]的其他规则

列表初始化序列L1一个更好的转换序列比列表初始化序列L2 ,如果

  • (3.1.1)L1转换为std​::​initializer_­list<X>for X L2但不转换,如果不是,则转换
  • (3.1.2)[...]

即使本条款的其他规定之一适用[示例:

void f1(int);                                 // #1
void f1(std::initializer_list<long>);         // #2
void g1() { f1({42}); }                       // chooses #2

void f2(std::pair<const char*, const char*>); // #3
void f2(std::initializer_list<std::string>);  // #4
void g2() { f2({"foo","bar"}); }              // chooses #4

 — 结束示例 ]

表示[over.ics.rank] /3.2[over.ics.rank] /3.3分别涉及通过标准转换序列和用户定义的转换序列来区分隐式转换序列的情况并不适用,这反过来意味着[在比较“ C2调用A”“ C2调用B”中的最佳匹配时,将不会使用over.ics.list] / 4[over.ics.list] / 9对隐式转换序列进行排名


这个话题是C ++ 11和C ++ 14中的缺陷

这些转换可能会违反直觉,并且控制它们的规则很复杂也就不足为奇了。在原来的C ++ 11和C ++ 14个的ISO标准的版本中,调用f({1.5F});实际上有暧昧的排名规则WRT的最佳可行函数,它浑身上下CWG缺陷报告1589 [重点煤矿]:

1589.列表初始化序列的模糊排序

栏目:16.3.3.2 [over.ics.rank]
状态:CD4
提交者:Johannes Schaub
日期:2012-11-21

[在2014年11月的会议上移至DR。]

当前的措词尚不清楚以下示例的解释:

void f(long);
void f(initializer_list<int>);
int main() { f({1L});

问题是列表初始化序列也可以是标准转换序列,具体取决于元素的类型和参数的类型,因此16.3.3.2中的列表中有多个项目符号[over.ics.rank]第3款适用

[...]

对于上面的示例,这些项目符号给出相反的结果,并且在其中选择了实现差异。

[...]

拟议决议(2014年6月):

通过问题1467的解决方案可以解决此问题

CWG缺陷报告1467最终还解决了DR 1589,特别是在上面的[over.ics.rank] / 3 [强调我的]中引用了相关部分

1467.来自同一类型对象的聚合的列表初始化

[...]

拟议决议(2014年6月):

[...]

  1. 将16.3.3.2 [over.ics.rank]第3段的最后一个项目符号移到列表的开头,并进行如下更改:

[...]

即使本段落中的其他规则之一适用。[示例:...-结束示例]

此决议还解决了问题1490、1589、1631、1756和1758。

此后,诸如GCC和Clang之类的编译器已将DR 1467向后移植到较早的标准(C ++ 11和C ++ 14)。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

使用std :: initializer_list构造函数而不会产生歧义?

std :: initializer_list构造函数

在constexpr上下文中验证std :: initializer_list

用`std :: enable_if`和非推导上下文重载函数模板

如何使用std :: initializer_list <double>构造一个构造函数

std :: initializer_list作为模板类中的构造函数参数

在std :: initializer_list的构造函数的参数列表中的折叠与“正常”折叠

构造函数中的std :: initializer_list转换

如何编写正确的std :: initializer_list构造函数

如何使用带有std :: initializer_list的构造函数设计类?

数组vs std :: initializer_list作为函数参数

采用 std::initializer_list 的构造函数优于其他构造函数

在上下文中使用数据构造函数

在非成员函数上使用delete有什么意义?

为什么使用带括号的初始化程序列表时首选std :: initializer_list构造函数?

如何不使用std :: initializer_list定义初始值设定项列表构造函数?

使用std :: initializer_list作为成员变量

在函数上下文中使用此关键字的Java语言回调

意外结果导致在使用biomaRt库的函数上下文中调用变量

std :: initializer_list构造函数和“初始化初始化”问题

显式构造函数和std :: initializer_list初始化

如何检查类型'T'是否具有'T(std :: initializer_list <U>)'构造函数

为c样式的字符串创建std :: initializer_list构造函数

C ++构造函数采用大小为一的std :: initializer_list

使用std :: bind与重载函数

如何使用std :: array构造函数参数C ++列表初始化const std :: array成员

在模板函数中使用initializer_list

构造函数使用std :: forward

使用来自就绪函数的参数将正确的上下文传递给函数