Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
245 views
in Technique[技术] by (71.8m points)

c++ - std::initializer_list as function argument

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"} );
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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-de?ned conversion sequence. User-de?ned 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-de?ned 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-quali?ed) X is considered for the ?rst 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 ?rst parameter is of type std::initializer_list<E> or reference to possibly cv-quali?ed 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 de?ned 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-de?ned 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 :)


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...