我有一个关于 C++ 初始化列表消歧的问题,它在 gcc、clang 和 Visual Studio 之间表现出不同的行为。
我想知道这是“未定义的行为”(不正确的程序)还是这些编译器之一有错误。任何的想法?
考虑以下声明:
class Arg
{
public:
Arg(int i);
};
class Object
{
public:
Object(const char* str, int i);
Object(const char* str, const std::initializer_list<Arg>& args);
};
Run Code Online (Sandbox Code Playgroud)
现在这种用法:
Object c("c", {4});
Run Code Online (Sandbox Code Playgroud)
应该使用哪个构造函数?一个 with int(假设文字周围的大括号是多余的)或一个带有初始化列表的(隐式转换 from intto Arg)。
GCC 10.2.0 选择具有初始化列表的构造函数Arg。
Clang 11.2.2-2 选择构造函数int并报告有关大括号的警告:
initlist.cpp:46:19: warning: braces around scalar initializer [-Wbraced-scalar-init]
Object c("c", {4});
^~~
Run Code Online (Sandbox Code Playgroud)
Visual Studio 2019 16.8.6 选择int没有警告的构造函数( /W4)。
从多数人的角度来看,具有int胜利的构造函数。另一方面,如果我们直接使用std::initializer_list<int> …
我正在尝试使用它std::initializer_list来定义和输出递归数据结构。在下面的示例中,我正在处理一个列表,其中每个元素可以是整数,也可以是同一类型列表的另一个实例。我使用中间变体类型来执行此操作,该中间变体类型可以是初始值设定项列表或整数。
我不清楚的是,它的生命周期是否std::initializer_list足够长来支持这个用例,或者我是否会遇到内存访问不一致的可能性。代码运行良好,但我担心这不能得到保证。我的希望是,用于创建最顶层列表的std::initializer_list任何中间临时std::initializer_list对象仅在顶层表达式完成后才被清除。
struct wrapped {
bool has_list;
int n = 0;
std::initializer_list<wrapped> lst;
wrapped(int n_) : has_list(false), n(n_) {}
wrapped(std::initializer_list<wrapped> lst_) : has_list(true), lst(lst_) {}
void output() const {
if (!has_list) {
std::cout << n << ' ';
} else {
std::cout << "[ ";
for (auto&& x : lst) x.output();
std::cout << "] ";
}
}
};
void call_it(wrapped w) {
w.output();
std::cout << std::endl;
}
void call_it() {
call_it({1}); // …Run Code Online (Sandbox Code Playgroud) 可以使用初始化列表创建结构/类的对象(没有构造函数).为什么在构造函数的 struct/class上不允许这样做?
struct r { int a; };
struct s { int a; s() : a(0) {} };
r = { 1 }; // works
s = { 1 }; // does not work
Run Code Online (Sandbox Code Playgroud) 我正在测试vs2013 c ++ initializer_list.
可以编译下面的代码.但是当我运行exe时崩溃了.
#include <memory>
#include <iostream>
class Base {};
class Derived : public Base {};
void DoSomething(std::initializer_list<std::shared_ptr<Base> > list)
{
}
int main()
{
auto ip = std::make_shared<Derived>();
std::cout << "use_count=" << ip.use_count() << std::endl;
DoSomething({ip, std::make_shared<Derived>()}); // ng
// DoSomething({ip, std::make_shared<Base>()}); // ok
// DoSomething({std::make_shared<Derived>(), ip}); // ok
std::cout << "use_count=" << ip.use_count() << std::endl;
}
Run Code Online (Sandbox Code Playgroud)
编译.
C:\...>cl.exe /EHsc test.cpp
Microsoft(R) C/C++ Optimizing Compiler Version 18.00.21005.1 for x86
Copyright (C) Microsoft Corporation. All rights reserved.
test.cpp …Run Code Online (Sandbox Code Playgroud) 在C++ 11中,初始化a似乎是合法的std::map<std::string, int>,如下所示:
std::map<std::string, int> myMap = {
{ "One", 1 },
{ "Two", 2 },
{ "Three", 3 }
};
Run Code Online (Sandbox Code Playgroud)
直观地看,这是有道理的-在括号内的初始化是对串的名单,并std::map<std::string, int>::value_type为std::pair<std::string, int>(可能有一些const资质.
但是,我不确定我是否理解这里的打字工作方式.如果我们在这里消除变量声明并且只是使用大括号括起初始化器,编译器就不会知道它正在查看a,std::initializer_list<std::pair<std::string, int>>因为它不知道被支撑的对代表std::pairs.因此,似乎编译器以某种方式推迟将类型赋值给括号封闭的初始化器,直到它从std::map构造函数中获得足够的类型信息来实现嵌套大括号用于对.我不记得在C++ 03中发生过这样的事情; 据我所知,表达式的类型从不依赖于它的上下文.
哪些语言规则允许此代码正确编译,以及编译器确定用于初始化列表的类型?我希望能够获得有关C++ 11规范的具体参考的答案,因为它的确很有意义!
谢谢!
c++ initializer-list language-lawyer c++11 list-initialization
我刚刚阅读并了解是否可以使用new运算符初始化C++ 11中的数组,但它并没有完全解决我的问题.
这段代码在Clang中给出了一个编译错误:
struct A
{
A(int first, int second) {}
};
void myFunc()
{
new A[1] {{1, 2}};
}
Run Code Online (Sandbox Code Playgroud)
我期望{{1,2}}使用单个元素初始化数组,然后使用构造函数args {1,2}初始化,但是我收到此错误:
error: no matching constructor for initialization of 'A'
new A[1] {{1, 2}};
^
note: candidate constructor not viable: requires 2 arguments, but 0 were provided
A(int first, int second) {}
^
note: candidate constructor (the implicit copy constructor) not viable: requires 1 argument, but 0 were provided
struct A
^
Run Code Online (Sandbox Code Playgroud)
为什么这种语法不起作用?
我想编写一个辅助函数,如:
template <typename F, typename Range1, typename Range2>
auto helper(const Range1& left, const Range2& right, F&& pred)
{
using namespace std; // for cbegin/cend and ADL
return pred(cbegin(left), cend(left), cbegin(right), cend(right));
}
Run Code Online (Sandbox Code Playgroud)
它适用于容器:
std::vector<int> v1 = {1,2,3,4,5,6};
std::vector<int> v2 = {5,4,3,2,1,6};
std::cout << helper(v1, v2, [](const auto&... args){ return std::is_permutation(args...);}) << std::endl;
Run Code Online (Sandbox Code Playgroud)
但它没有推断initializer_list--s(例子):
std::cout << helper({1,2,3,4,5,6}, {5,4,3,2,1,6}, [](const auto&... args){ return std::is_permutation(args...);}) << std::endl;
Run Code Online (Sandbox Code Playgroud)
是否有一种惯用的重写方式,helper以便它推导出容器和initializer_list-s?
对于容器和容器的所有组合,initializer_list我无法想出任何比重载更好的东西.
我有一些非常复杂的物体。它们包含其他对象的成员变量。我理解复制构造函数级联的美妙之处,这样默认的复制构造函数通常就可以工作。但是,最常破坏默认复制构造函数的情况(对象包含一些成员变量,它们是指向其其他成员变量的指针)仍然适用于我构建的很多内容。这是我的一个对象、它的构造函数和我编写的复制构造函数的示例:
struct PhaseSpace {
PhaseSpace();
private:
std::string file_name; ///< Name of the file from which these coordinates (and
///< perhaps velocities) derived. Empty string indicates
///< no file.
int atom_count; ///< The number of atoms in the system
UnitCellType unit_cell; ///< The type of unit cell
Hybrid<double> x_coordinates; ///< Cartesian X coordinates of all particles
Hybrid<double> y_coordinates; ///< Cartesian Y coordinates of all particles
Hybrid<double> z_coordinates; ///< Cartesian Z coordinates of all particles
Hybrid<double> box_space_transform; ///< Matrix to transform coordinates …Run Code Online (Sandbox Code Playgroud) 在下面的程序中,structC有两个构造函数:一个来自std::initializer_list<A>,另一个来自std::initializer_list<B>。然后使用以下命令创建该结构的对象C{{1}}:
#include <initializer_list>
struct A {
int i;
};
struct B {
constexpr explicit B(int) {}
};
struct C {
int v;
constexpr C(std::initializer_list<A>) : v(1) {}
constexpr C(std::initializer_list<B>) : v(2) {}
};
static_assert( C{{1}}.v == 1 );
Run Code Online (Sandbox Code Playgroud)
由于只能A从 隐式构造聚合int,因此可以预期这C(std::initializer_list<A>)是首选并且程序成功。在 Clang 中确实如此。
然而海湾合作委员会抱怨:
error: call of overloaded 'C(<brace-enclosed initializer list>)' is ambiguous
note: candidate: 'constexpr C::C(std::initializer_list<B>)'
note: candidate: 'constexpr C::C(std::initializer_list<A>)'
Run Code Online (Sandbox Code Playgroud)
MSVC 也是如此:
error C2440: '<function-style-cast>': cannot …Run Code Online (Sandbox Code Playgroud) 方便的initializer_list语法似乎是以无法移动列表成员、创建不必要的副本为代价的。
struct A
{
// some members which are dynamic resources...
A() { cout << "Default Constructor\n"; }
A(const A& original) { cout << "Copy constructor\n"; }
A(A&& original) { cout << "Move constructor\n"; }
};
int main() {
vector<A> v1{ A() , A() }; // calls copy
vector<A> v2;
v2.push_back(A()); v2.push_back(A()); // calls move
return 0;
}
Run Code Online (Sandbox Code Playgroud)
如果我理解正确,这是因为取消引用初始化器迭代器会给出const T,即使尝试移动时也会复制它。
有解决方法吗?
阅读/sf/answers/3101541901/,提出了一种使用可变参数模板的解决方案,如下所示:
template<class Array> struct maker;
// a maker which makes a std::vector
template<class …Run Code Online (Sandbox Code Playgroud) c++ ×10
initializer-list ×10
c++11 ×2
arrays ×1
c++14 ×1
c++17 ×1
c++20 ×1
c++23 ×1
clang ×1
class ×1
compiler-bug ×1
constructor ×1
struct ×1
templates ×1