当此构造函数采用初始化列表并委托向量时,是什么导致此构造函数委托给自身?
What causes this constructor to delegate to itself when it takes an initializer list and delegates a vector?
#include <initializer_list>
#include <vector>
struct test
{
using t = std::vector<test>;
test(t const &v)
{
}
test(t &&v)
{
}
test(std::initializer_list<test> v)
: test{t{v}} //error
{
}
};
Clang and GCC 都抱怨第三个构造函数,即采用初始化列表的构造函数委托给自己。我不明白这是怎么可能的,因为你不能从向量构造一个初始化列表。
通过用圆括号替换外部花括号来修复错误是微不足道的,但为什么这首先会成为一个问题?这个几乎相同的程序编译得很好:
#include <initializer_list>
struct a {};
struct b {};
struct test
{
test(a const &)
{
}
test(a &&)
{
}
test(std::initializer_list<b> v)
: test{a{}} //no error, still using curly braces
{
}
};
有趣的是,对于上面的第二个示例,如果将 b
替换为 test
,错误会再次出现。有人可以解释这里发生了什么吗?
您的代码中的问题是您正在编写一个调用自身的函数。
test(std::initializer_list<test> v)
: test{t{v}} //error
{
}
test{t{v}}
将首先以 v 作为参数调用 vector<test>
的初始化列表。但是 vector
将再次调用该函数来初始化将失败的值。
编译器不知道如何解决这个问题。将初始化更改为使用括号将(如您所说)解决此问题,因为它将在 vector
中调用隐式复制构造函数(什么都不做,因为您的结构没有做任何事情)。
第二个示例以此调用开始:a{}
它很好地解决了,因为 a
是一个具有隐式定义构造函数的基本结构。第二个电话是 test{...}
一个
因为类型是 a
并且有 a
的构造函数所以它运行得很好。
t{v}
的类型是std::vector<test>
。这个想法是 init-list 构造函数总是优先于任何其他构造函数,因此 test{t{v}}
将首先尝试调用 init-list 构造函数,如果存在,并且类型兼容。在你的情况下,这是可能的,因为 test
本身可以从 std::vector<test>
隐式构造(通过你的前 2 个构造函数),所以编译器最终委托给 init-list 构造函数,因此错误.
在第二种情况下,没有歧义,因为类型 a{}
不能再隐式转换为 std::initializer_list<b>
.
在第一个示例中构造构造函数 explicit
,或者用 test(t{v})
调用基构造函数,您的歧义将消失(编译器将不再执行隐式转换)。
一个更简单的示例(实时 here)表现出与您的第一个示例基本相同的行为是:
#include <initializer_list>
struct test
{
/*explicit*/ test(int){} // uncomment explicit and no more errors
test( std::initializer_list<test> v)
: test{42} {} // error, implicitly converts 42 to test(42) via the test(int)
};
int main(){}
处理 init-list 构造函数的标准的相关部分是 §13.3.1.7/1 [over.match.list] - 下面的引文摘自@Praetorian 现在已删除的答案 -
When objects of non-aggregate class type T are list-initialized such that 8.5.4 specifies that overload resolution is performed according to the rules in this section, overload resolution selects the constructor in two phases:
— Initially, the candidate functions are the initializer-list constructors (8.5.4) of the class T
and the argument list consists of the initializer list as a single argument.
— If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the class T
and the argument list consists of the elements
of the initializer list.
我不是 C++ 专家,但主要区别在于
test{a{}}
initializer_list<a>
没有重载,因此构造函数不可用。
另一方面,test{t{v}}
确实有一个可用的 initializer_list<test>
构造函数,因为您可以从 vector 创建一个 test
。它可以使用(我不知道规则的名称)1 cast 转换。
test{t{v}} -> test{test(t{v})}
#include <initializer_list>
#include <vector>
struct test
{
using t = std::vector<test>;
test(t const &v)
{
}
test(t &&v)
{
}
test(std::initializer_list<test> v)
: test{t{v}} //error
{
}
};
Clang and GCC 都抱怨第三个构造函数,即采用初始化列表的构造函数委托给自己。我不明白这是怎么可能的,因为你不能从向量构造一个初始化列表。
通过用圆括号替换外部花括号来修复错误是微不足道的,但为什么这首先会成为一个问题?这个几乎相同的程序编译得很好:
#include <initializer_list>
struct a {};
struct b {};
struct test
{
test(a const &)
{
}
test(a &&)
{
}
test(std::initializer_list<b> v)
: test{a{}} //no error, still using curly braces
{
}
};
有趣的是,对于上面的第二个示例,如果将 b
替换为 test
,错误会再次出现。有人可以解释这里发生了什么吗?
您的代码中的问题是您正在编写一个调用自身的函数。
test(std::initializer_list<test> v)
: test{t{v}} //error
{
}
test{t{v}}
将首先以 v 作为参数调用 vector<test>
的初始化列表。但是 vector
将再次调用该函数来初始化将失败的值。
编译器不知道如何解决这个问题。将初始化更改为使用括号将(如您所说)解决此问题,因为它将在 vector
中调用隐式复制构造函数(什么都不做,因为您的结构没有做任何事情)。
第二个示例以此调用开始:a{}
它很好地解决了,因为 a
是一个具有隐式定义构造函数的基本结构。第二个电话是 test{...}
一个
因为类型是 a
并且有 a
的构造函数所以它运行得很好。
t{v}
的类型是std::vector<test>
。这个想法是 init-list 构造函数总是优先于任何其他构造函数,因此 test{t{v}}
将首先尝试调用 init-list 构造函数,如果存在,并且类型兼容。在你的情况下,这是可能的,因为 test
本身可以从 std::vector<test>
隐式构造(通过你的前 2 个构造函数),所以编译器最终委托给 init-list 构造函数,因此错误.
在第二种情况下,没有歧义,因为类型 a{}
不能再隐式转换为 std::initializer_list<b>
.
在第一个示例中构造构造函数 explicit
,或者用 test(t{v})
调用基构造函数,您的歧义将消失(编译器将不再执行隐式转换)。
一个更简单的示例(实时 here)表现出与您的第一个示例基本相同的行为是:
#include <initializer_list>
struct test
{
/*explicit*/ test(int){} // uncomment explicit and no more errors
test( std::initializer_list<test> v)
: test{42} {} // error, implicitly converts 42 to test(42) via the test(int)
};
int main(){}
处理 init-list 构造函数的标准的相关部分是 §13.3.1.7/1 [over.match.list] - 下面的引文摘自@Praetorian 现在已删除的答案 -
When objects of non-aggregate class type T are list-initialized such that 8.5.4 specifies that overload resolution is performed according to the rules in this section, overload resolution selects the constructor in two phases:
— Initially, the candidate functions are the initializer-list constructors (8.5.4) of the classT
and the argument list consists of the initializer list as a single argument.
— If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the classT
and the argument list consists of the elements of the initializer list.
我不是 C++ 专家,但主要区别在于
test{a{}}
initializer_list<a>
没有重载,因此构造函数不可用。
另一方面,test{t{v}}
确实有一个可用的 initializer_list<test>
构造函数,因为您可以从 vector 创建一个 test
。它可以使用(我不知道规则的名称)1 cast 转换。
test{t{v}} -> test{test(t{v})}