std::initializer_list 作为函数参数

发布于 2024-08-24 03:54:03 字数 501 浏览 8 评论 0原文

出于某种原因,我认为 C++0x 允许 std::initializer_list 作为期望可以从中构造类型的函数的函数参数,例如 std::vector。但显然,它不起作用。这只是我的编译器,还是永远不会工作?是因为潜在的重载解决问题吗?

#include <string>
#include <vector>

void function(std::vector<std::string> vec)
{
}

int main()
{
    // ok
    std::vector<std::string> vec {"hello", "world", "test"};

    // error: could not convert '{"hello", "world", "test"}' to 'std::vector...'
    function( {"hello", "world", "test"} );
}

For some reason I thought C++0x allowed std::initializer_list as function argument for functions that expect types that can be constructed from such, for example std::vector. But apparently, it does not work. Is this just my compiler, or will this never work? Is it because of potential overload resolution problems?

#include <string>
#include <vector>

void function(std::vector<std::string> vec)
{
}

int main()
{
    // ok
    std::vector<std::string> vec {"hello", "world", "test"};

    // error: could not convert '{"hello", "world", "test"}' to 'std::vector...'
    function( {"hello", "world", "test"} );
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

睫毛上残留的泪 2024-08-31 03:54:03

海湾合作委员会有一个错误。该标准使这一点有效。请参阅:

请注意,此问题有两个方面:

  • 一般如何进行初始化以及进行什么初始化?
  • 在重载决议期间如何使用初始化,它有什么成本?

第一个问题在 8.5 部分得到了解答。第二个问题在13.3部分得到解答。例如,引用绑定在 8.5.313.3.3.1.4 中处理,而列表初始化在 8.5.4 和 <代码>13.3.3.1.5。

8.5/14,16

表单中发生的初始化

T x = a;

以及参数传递、函数返回、引发异常 (15.1)、处理异常 (15.3) 和聚合成员初始化 (8.5.1) 称为复制初始化。< br>
.
.
初始化器的语义如下[...]:如果初始化器是花括号初始化列表,则该对象是列表初始化的(8.5.4)。

当考虑候选函数时,编译器将看到一个初始化列表(它还没有类型 - 它只是一个语法构造!)作为参数,以及一个 std::vector作为function 的参数。为了弄清楚转换成本是多少以及我们是否可以在重载的情况下转换这些成本,13.3.3.1/5 表示

13.3.3.1.5/1 :

当参数是初始值设定项列表 (8.5.4) 时,它不是表达式,并且应用特殊规则将其转换为参数类型。

13.3.3.1.5/3:

否则,如果参数是非聚合类 X 并且根据 13.3.1.7 的重载决策选择 X 的单个最佳构造函数来执行参数初始值设定项列表中类型 X 的对象的初始化,则隐式转换顺序为用户定义的转换序列。允许用户定义的转换将初始值设定项列表元素转换为构造函数参数类型,除非 13.3.3.1 中另有说明。

非聚合类Xstd::vector,我将在下面找出最好的构造函数。最后一条规则允许我们在以下情况下使用用户定义的转换:

struct A { A(std::string); A(A const&); };
void f(A);
int main() { f({"hello"}); }

我们可以将字符串文字转换为 std::string,即使这需要用户定义的转换。然而,它指出了另一段的限制。 13.3.3.1 说什么?

13.3.3.1/4,这是负责禁止多个用户定义转换的段落。我们只看列表初始化:

但是,当考虑用户定义的转换函数 [(或构造函数)] 的参数时,该参数是 [...] 13.3.1.7 的候选,当将初始值设定项列表作为单个参数传递或当初始值设定项列表具有恰好一个元素和到某个类 X 的转换或对(可能是 cv 限定的)X 的引用被视为 X 构造函数的第一个参数,或者 [...],只允许标准转换序列和省略号转换序列。

请注意,这是一个重要的限制:如果不是这样,上面的代码可以使用复制构造函数来建立同样良好的转换序列,并且初始化将是不明确的。 (请注意该规则中“A 或 B 和 C”的潜在混淆:它的意思是“(A 或 B)和 C” - 所以我们在尝试通过 a 进行转换时受到限制X 的构造函数具有 X 类型的参数)。

我们被委托给 13.3.1.7 来收集可用于执行此转换的构造函数。让我们从一般的角度来看待这一段,从 8.5 开始,它委托我们到 8.5.4

8.5.4/1

列表初始化可以发生在直接初始化或复制初始化上下文中;直接初始化上下文中的列表初始化称为“直接列表初始化”,而复制初始化上下文中的列表初始化称为“复制列表初始化”。

8.5.4/2:

如果构造函数的第一个参数的类型为 std::initializer_list 或对可能 cv 限定的 std 的引用,则构造函数是初始化列表构造函数 ::initializer_list 对于某些类型 E,并且要么没有其他参数,要么所有其他参数都有默认参数 (8.3.6)。

8.5.4/3:

类型 T 的对象或引用的列表初始化定义如下: [...] 否则,如果 T 是类类型,则考虑构造函数。如果 T 有一个初始化列表构造函数,则参数列表由初始化列表作为单个参数组成;否则,参数列表由初始值设定项列表的元素组成。枚举适用的构造函数(13.3.1.7),并通过重载决议选择最好的构造函数(13.3)。

此时,T 就是类类型std::vector。我们有一个参数(它还没有类型!我们只是在一个语法初始化列表的上下文中)。从 13.3.1.7 开始枚举构造函数:

[...] 如果 T 有一个初始值设定项列表构造函数 (8.5.4),则参数列表由初始值设定项列表作为单个参数组成;否则,参数列表由初始值设定项列表的元素组成。对于复制列表初始化,候选函数都是 T 的构造函数。但是,如果选择显式构造函数,则初始化是格式错误的。

我们只会将 std::vector 的初始值设定项列表视为唯一候选者,因为我们已经知道其他列表不会赢得它或不适合该参数。它具有以下签名:

vector(initializer_list<std::string>, const Allocator& = Allocator());

现在,将初始值设定项列表转换为 std::initializer_list 的规则(对参数/参数转换的成本进行分类)在 13.3 中枚举.3.1.5

当参数是初始值设定项列表 (8.5.4) 时,它不是表达式,并且适用特殊规则将其转换为参数类型。 [...] 如果参数类型为 std::initializer_list并且初始化器列表的所有元素都可以隐式转换为 X,则隐式转换序列是转换所需的最差转换列表的一个元素到 X。此转换可以是用户定义的转换,即使在调用初始化列表构造函数的上下文中也是如此。

现在,初始化列表将被成功转换,并且转换序列是用户定义的转换(从 char const[N] 到 std::string )。这是如何实现的,在 8.5.4 中再次详细说明:

否则,如果 T 是 std::initializer_list 的特化,则按如下所述构造一个initializer_list 对象,并根据以下对象初始化规则来初始化该对象:相同类型的类(8.5)。 (...)

请参阅 8.5.4/4 最后一步是如何完成的:)

GCC has a bug. The Standard makes this valid. See:

Notice that there are two sides of this

  • How and what initialization is done in general?
  • How is initialization used during overload resolution, and what cost does it have?

The first question is answered in section 8.5. The second question is answered in section 13.3. For example, reference binding is handled at 8.5.3 and 13.3.3.1.4, while list initialization is handled in 8.5.4 and 13.3.3.1.5.

8.5/14,16:

The initialization that occurs in the form

T x = a;

as well as in argument passing, function return, throwing an exception (15.1), handling an exception (15.3), and aggregate member initialization (8.5.1) is called copy-initialization.
.
.
The semantics of initializers are as follows[...]: If the initializer is a braced-init-list, the object is list-initialized (8.5.4).

When considering the candidate function, the compiler will see an initializer list (which has no type yet - it's just a grammatical construct!) as the argument, and a std::vector<std::string> as the parameter of function. To figure out what the cost of conversion is and whether we can convert these in context of overloading, 13.3.3.1/5 says

13.3.3.1.5/1:

When an argument is an initializer list (8.5.4), it is not an expression and special rules apply for converting it to a parameter type.

13.3.3.1.5/3:

Otherwise, if the parameter is a non-aggregate class X and overload resolution per 13.3.1.7 chooses a single best constructor of X to perform the initialization of an object of type X from the argument initializer list, the implicit conversion sequence is a user-defined conversion sequence. User-defined conversions are allowed for conversion of the initializer list elements to the constructor parameter types except as noted in 13.3.3.1.

The non-aggregate class X is std::vector<std::string>, and i will figure out the single best constructor below. The last rule grants us to use user defined conversions in cases like the following:

struct A { A(std::string); A(A const&); };
void f(A);
int main() { f({"hello"}); }

We are allowed to convert the string literal to std::string, even if this needs a user defined conversion. However, it points to restrictions of another paragraph. What does 13.3.3.1 say?

13.3.3.1/4, which is the paragraph responsible for forbidding multiple user defined conversions. We will only look at list initializations:

However, when considering the argument of a user-defined conversion function [(or constructor)] that is a candidate by [...] 13.3.1.7 when passing the initializer list as a single argument or when the initializer list has exactly one element and a conversion to some class X or reference to (possibly cv-qualified) X is considered for the first parameter of a constructor of X, or [...], only standard conversion sequences and ellipsis conversion sequences are allowed.

Notice that this is an important restriction: If it weren't for this, the above can use the copy-constructor to establish an equally well conversion sequence, and the initialization would be ambiguous. (notice the potential confusion of "A or B and C" in that rule: It is meant to say "(A or B) and C" - so we are restricted only when trying to convert by a constructor of X having a parameter of type X).

We are delegated to 13.3.1.7 for collecting the constructors we can use to do this conversion. Let's approach this paragraph from the general side starting from 8.5 which delegated us to 8.5.4:

8.5.4/1:

List-initialization can occur in direct-initialization or copy-initialization contexts; list-initialization in a direct-initialization context is called direct-list-initialization and list-initialization in a copy-initialization context is called copy-list-initialization.

8.5.4/2:

A constructor is an initializer-list constructor if its first parameter is of type std::initializer_list<E> or reference to possibly cv-qualified std::initializer_list<E> for some type E, and either there are no other parameters or else all other parameters have default arguments (8.3.6).

8.5.4/3:

List-initialization of an object or reference of type T is defined as follows: [...] Otherwise, if T is a class type, constructors are considered. If T has an initializer-list constructor, the argument list consists of the initializer list as a single argument; otherwise, the argument list consists of the elements of the initializer list. The applicable constructors are enumerated (13.3.1.7) and the best one is chosen through overload resolution (13.3).

At this time, T is the class type std::vector<std::string>. We have one argument (which does not have a type yet! We are just in the context of having a grammatical initializer list). Constructors are enumerated as of 13.3.1.7:

[...] If T has an initializer-list constructor (8.5.4), the argument list consists of the initializer list as a single argument; otherwise, the argument list consists of the elements of the initializer list. For copy-list-initialization, the candidate functions are all the constructors of T. However, if an explicit constructor is chosen, the initialization is ill-formed.

We will only consider the initializer list of std::vector as the only candidate, since we already know the others won't win against it or won't fit the argument. It has the following signature:

vector(initializer_list<std::string>, const Allocator& = Allocator());

Now, the rules of converting an initializer list to an std::initializer_list<T> (to categorize the cost of the argument/parameter conversion) are enumerated in 13.3.3.1.5:

When an argument is an initializer list (8.5.4), it is not an expression and special rules apply for converting it to a parameter type. [...] If the parameter type is std::initializer_list<X> and all the elements of the initializer list can be implicitly converted to X, the implicit conversion sequence is the worst conversion necessary to convert an element of the list to X. This conversion can be a user-defined conversion even in the context of a call to an initializer-list constructor.

Now, the initializer list will be successfully converted, and the conversion sequence is a user defined conversion (from char const[N] to std::string). How this is made is detailed at 8.5.4 again:

Otherwise, if T is a specialization of std::initializer_list<E>, an initializer_list object is constructed as described below and used to initialize the object according to the rules for initialization of an object from a class of the same type (8.5). (...)

See 8.5.4/4 how this final step is made :)

就是爱搞怪 2024-08-31 03:54:03

它似乎是这样工作的:

function( {std::string("hello"), std::string("world"), std::string("test")} );

也许这是一个编译器错误,但也许您要求太多隐式转换。

It seems to work this way:

function( {std::string("hello"), std::string("world"), std::string("test")} );

Perhaps it is a compiler bug, but perhaps you are asking for too many implicit conversions.

一场信仰旅途 2024-08-31 03:54:03

顺便说一句,我不确定,但我怀疑这里发生的事情是转换为initializer_list是一种转换,而将其转换为向量是另一种转换。如果是这种情况,那么您就超出了仅一次隐式转换的限制......

Offhand, I'm not sure, but I suspect what's going on here is that converting to an initializer_list is one conversion, and converting that to vector is another conversion. If that's the case, you're exceeding the limit of only one implicit conversion...

爱的那么颓废 2024-08-31 03:54:03

这要么是编译器错误,要么是您的编译器不支持 std::initializer_list。在 GCC 4.5.1 上测试,编译良好。

This is either a compiler bug or your compiler doesn't support std::initializer_list. Tested on GCC 4.5.1 and it compiles fine.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文