从括号内的initializer_list构造时,调用了错误的重载 [英] Wrong overload called when constructing from initializer_list inside parentheses

查看:113
本文介绍了从括号内的initializer_list构造时,调用了错误的重载的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直以为,当我使用初始化列表C ++语法时:

I always thought that when I use initializer list C++ syntax like:

something({ ... });

对于编译器来说,我一直很想调用std::initializer_list来调用重载,但是对于MSVC 2015来说似乎不太清楚.

it's always clear to the compiler that I want to call the overload taking an std::initializer_list, but it seems this is not so clear for MSVC 2015.

我测试了以下简单代码:

I tested this simple code:

#include <cstdio>
#include <initializer_list>

namespace testing {
  template<typename T>
  struct Test {
    Test() {
      printf("Test::Test()\n");
    }

    explicit Test(size_t count) {
      printf("Test::Test(int)\n");
    }

    Test(std::initializer_list<T> init) {
      printf("Test::Test(std::initializer_list)\n");
    }

    T* member;
  };

  struct IntSimilar {
    int val;

    IntSimilar() : val(0) {}
    IntSimilar(int v) : val(v) {}

    operator int() {
      return val;
    }
  };
}

int main() {
    testing::Test<testing::IntSimilar> obj({ 10 });
    return 0;
}

运行

,并且在GCC 6.3中可以正常工作,调用Test::Test(std::initializer_list)

and in GCC 6.3 it works as expected, calling Test::Test(std::initializer_list)

但是在MSVC 2015中,此代码称为Test::Test(int).

but in MSVC 2015 this code calls Test::Test(int).

似乎MSVC可以以某种方式忽略{}并选择一个无效/意外的重载进行调用.

It seems MSVC can somehow ignore the {} and choose an invalid/unexpected overload to call.

标准对这种情况有何看法?哪个版本有效?

What does the Standard say about this situation? Which version is valid?

任何人都可以对此进行测试,并确认此问题是否仍存在于MSVC 2017中吗?

Can anybody test this and confirm whether or not this issue remains in MSVC 2017?

推荐答案

哪个版本有效?

Which version is valid?

根据我对标准的了解,海湾合作委员会(GCC)正确.

According to my understanding of the standard, the GCC is right.

标准对此情况有何说明?

What does standard says about this situation?

在编写Test obj1({10});时,您要做的是 直接初始化 一个类型为Test且表达式为{ 10 }的对象.在重载解析期间,编译器必须决定要调用哪个构造函数.根据 16.3.3.2§3(3.1.1 )[over.ics.rank] :

What you do when you are writing Test obj1({10}); is direct-initializing an object of type Test with the expression { 10 }. During overload resolution, the compiler has to decide which constructor to call. According to 16.3.3.2 § 3 (3.1.1) [over.ics.rank]:

如果L1转换为

list-initialization 序列L1比列表初始化序列L2更好的转换序列 std::initializer_list<X>对于某些XL2并不[...]

list-initialization sequence L1 is a better conversion sequence than list-initialization sequence L2 if L1 converts to std::initializer_list<X> for some X and L2 does not [...]

该标准还提供了示例

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

这就是VS& clang与GCC有所不同:虽然在此特定示例中所有这三种方法都会产生相同的结果,但将其更改为

This is the point where VS & clang differ from GCC: while all three will yield the same result in this particular example, changing it to

#include <iostream>

struct A { A(int) { } };
void f1(int) { std::cout << "int\n"; }                                // #1
void f1(std::initializer_list<A>) { std::cout << "list\n"; }          // #2

int main() {
    f1({42});
}

将让 clang选择int -constructor ,抱怨在文字42(出于遗留原因,这似乎只是标准中的内容,请参见此处),而不是检查{ 42 }列表序列是否真的不能转换为std::initializer_list<A>.

will let clang chose the int-constructor, moaning about the unnecessary braces around the literal 42 (which seems to be just in the standard for legacy reasons, see here) rather than checking if the { 42 } list sequence really cannot be converted to std::initializer_list<A>.

但是请注意,编写Test obj1{ 10 };会导致不同的评估:根据 列表初始化 :

Note however that writing Test obj1{ 10 }; will lead to a different evaluation: According to the rules of list-initialization:

  • 否则,分两个阶段考虑T的构造函数:
    • 检查所有将std :: initializer_list作为唯一参数,或将第一个参数(如果其余参数具有默认值)作为第一个参数的构造函数,并通过重载解析将其与std :: initializer_list类型的单个参数进行匹配
  • Otherwise, the constructors of T are considered, in two phases:
    • All constructors that take std::initializer_list as the only argument, or as the first argument if the remaining arguments have default values, are examined, and matched by overload resolution against a single argument of type std::initializer_list

因此,将initializer_list构造函数用于特殊的重载解决方案阶段,在应用常规重载解决方案之前仅考虑initializer_list构造函数,如著名的std::vector -gotcha:

So the initializer_list constructor is taken for a special overload resolution stage considering only initializer_list constructors before the normal overload resolution is applied, as demonstrated in the famous std::vector-gotcha:

// will be a vector with elements 2, 0 rather than a vector of size 2 with values 0, 0
std::vector<int> v{ 2, 0 };

在两种情况下,标准都决定使用initializer_list构造函数是一个一致的选择,但是从技术上讲,选择它的原因在幕后完全不同.

The fact that in both cases the standard decides to use the initializer_list constructor is a consistent choice, but technically, the reason for chosing it is quite different under the hood.

这篇关于从括号内的initializer_list构造时,调用了错误的重载的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆