c++ - 从括号内的 initializer_list 构造时调用了错误的重载

标签 c++ c++11 visual-c++ language-lawyer clang++

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

something({ ... });

编译器总是很清楚我想调用采用 std::initializer_list 的重载,但对于 MSVC 2015 似乎不太清楚。

我测试了这个简单的代码:

#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;
}

Run

在 GCC 6.3 中,它按预期工作,调用 Test::Test(std::initializer_list)

但在 MSVC 2015 中,此代码调用 Test::Test(int)

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

标准对这种情况怎么说?哪个版本有效?

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

最佳答案

Which version is valid?

根据我对标准的理解,GCC 是对的

What does standard says about this situation?

你在写作时做了什么Test obj1({10});direct-initializing Test 类型的对象用表达式 { 10 } .在重载决议期间,编译器必须决定调用哪个构造函数。根据16.3.3.2 § 3 (3.1.1) [over.ics.rank] :

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 的不同之处:虽然在这个特定示例中所有三个都会产生相同的结果,但将其更改为

#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 chose the int -constructor ,提示文字 42 周围不必要的大括号(由于遗留原因,这似乎只是在标准中,请参阅 here )而不是检查 { 42 } 是否存在。列表序列确实无法转换为std::initializer_list<A> .

但是请注意写作 Test obj1{ 10 };会导致不同的评价:根据list-initialization的规则:

  • 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 中所示-陷阱:

// 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 constructor 是一个一致的选择,但从技术上讲,选择它的原因在幕后是完全不同的。

关于c++ - 从括号内的 initializer_list 构造时调用了错误的重载,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47608388/

相关文章:

c++ - ASN1C编译

c++ - 如何使用 Qt 打开网络摄像头并捕获图像并将其保存在系统上

c++ - 错误 : declaration of ‘data’ as array of references

c++ - 这个重载决议是否正确?

c++ - 将对象传递给将创建它的函数时使用什么合适的智能指针

c++ - C++1y 中是否需要公共(public)类内类型定义?

c++ - 使用 g++ 编译器编译时如何添加 mclmcrrt.lib 或 (.lib) 文件

需要 C++ 代码说明

c++ - Visual C++ 中的分布式构建工具?

c++ - 条件表达式中的常量值