7
votes
#include <iostream>
#include <string>
#include <typeinfo>
#include <typeindex>
#include <map>
#include <vector>

class Base{
public:
    virtual ~Base() {}

};

class Derived: public Base { };

int main(){

    int arr[10];
    Derived d;
    Base *p = &d;

    std::map<std::type_index, std::string> proper_name = {
        {typeid(int), "int"}, {typeid(double), "double"}, {typeid(float), "float"}, {typeid(char), "char"},
        {typeid(Base), "Base"}, {typeid(Derived), "Derived"}, {typeid(std::string), "String"},
        {typeid(int[10]), "Ten int Array"}, {typeid(p), "Base Pointer"}};

}

I'm trying to make sense of the implicit conversions that occur in this list-initialization. From 13.3.1.7 of N3337:

When objects of non-aggregate class type T are list-initialized (8.5.4), overload resolution selects the constructor in two phases:

  1. 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.

  2. 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.

8.5.4:

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

So the following list of constructors for std::map indicates

map (initializer_list<value_type> il, const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type());

is the candidate function, value_type in this case is pair<const type_index, std::string>. Lastly from 13.3.3.1.5:

If the parameter type is std::initializer_list<X> or “array of X”135 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.

So it is a valid conversion as long as the elements of the braced-list implicitly convert to pair<const type_index, std::string>. But those elements are also braced-lists themselves. Pair does not take an initializer-list constructor, from here it seems that copy-initialization from a braced-init list uses the second part of 13.3.1.7 to construct the object. So the following:

pair<const type_index, std::string> p = {typeid(int), "int"}

becomes:

pair<const type_index, std::string> p(typeid(int), "int")

but is this considered an implicit conversion? How can use of a two-argument constructor be considered an implicit conversion? What are the standard's comments on this?

1

1 Answers

3
votes

Your conclusion that

pair<const type_index, std::string> p = {typeid(int), "int"};

becomes

pair<const type_index, std::string> p(typeid(int), "int");

is not accurate because the first statement is copy-list-initialization while the second is direct-initialization. The two are identical, except that copy-list-initialization is ill-formed if an explicit constructor is chosen (and narrowing conversions are not allowed in the former).

So if the pair constructor in question was defined as

template<class U1, class U2>
explicit constexpr pair(U1&& x, U2&& y);

direct-initialization would still succeed, but copy-list-initialization would fail. Quoting from right below the parts of [over.match.list] you quoted

In copy-list-initialization, if an explicit constructor is chosen, the initialization is ill-formed.


Other than that, everything else you've said is correct. The pair constructor is an implicit conversion because the constructor is not explicit and it's considered for overload resolution according to the second bullet of [over.match.list] because pair doesn't have an initializer list constructor.