Topic: Brace initializers and template type deduction


Author: Scott Meyers <usenet@aristeia.com>
Date: Tue, 10 Nov 2009 17:01:59 CST
Raw View
Faisal Vali wrote:
> I thought that the following code would not work with list-
> initialization per
> the standard (my variadic syntax might be a little off):
>
> struct Widget {
>  template<class T>
>  Widget(T&&  t)  { std:: cout << t << std::endl; }
>  template<class First, class ... Rest>
>  Widget(First&&  f, Rest&& ... r) : Widget(r...)
>  { std::cout << f << std::endl; }
> };
>
> Widget w{1,2,3};  // not ok,
>
> Widget w(1,2,3); // ok
>
> If the list-initialization case is ill-formed, I think that is
> especially
> unfortunate since list-initialization is supposed to do the right and
> intuitive
> thing each time (and minimize asymmetry) - but in this case it
> introduces
> asymmetry (with template constructors).

My conclusion about brace initializer lists is that they are interpreted as
initializers for std::initializer_list objects if at all possible.
Only if this
interpretation is not possible does the compiler break them into their
constituent parts and look for a constructor taking that many arguments.  That
is, the "right and intuitive thing" is to interpret { a, b, c }  as an
initializer for a std::initializer_list<T> object, and only if there is no
std::initializer_list object to try to match is a constructor taking some other
type considered.

To me, the asymmetry is between template type deduction and auto type
deduction.
  I don't understand why

   auto x = { 1, 2, 3 };

deduces x's type to be a std::initializer_list<int>, but

   template<typename T>
   void f(T x);

   f({1, 2, 3});

won't deduce T to be std::initializer_list<int>.

> It would be convenient to be able to write:
>
>  template<class T, int N> make_array(T (&a)[N])
>  { return array<T,N>(a, a+N); }
>
>  auto a = make_array({1,2,3}}; // Not ok!
>
>  This is especially suprising because the accepted (albeit non-
> normative)
>  rewrite rule for initializer lists is that they can be written as
> arrays.
>  Yet you cannot initialize a reference to an array with it directly.

It sounds like what you'd like is for essentially the same rules to
apply during
template argument deduction given a brace initializer argument as currently
apply during initialization of objects of a known type.  I don't think
that's an
unreasonable desire, but I don't know if there are technical issues that would
make it untenable, and at any rate, it seems like the committee has gone in a
different direction.

Scott

--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Faisal Vali <faisalv@gmail.com>
Date: Wed, 11 Nov 2009 12:07:25 CST
Raw View
On Nov 10, 5:01 pm, Scott Meyers <use...@aristeia.com> wrote:
> My conclusion about brace initializer lists is that they are interpreted as
> initializers for std::initializer_list objects if at all possible.
> Only if this
> interpretation is not possible does the compiler break them into their
> constituent parts and look for a constructor taking that many arguments.  That
> is, the "right and intuitive thing" is to interpret { a, b, c }  as an
> initializer for a std::initializer_list<T> object, and only if there is no
> std::initializer_list object to try to match is a constructor taking some other
> type considered.

This is my conclusion too.

But I find nothing in the language of the standard that supports the
following type deduction:

struct A {
 template<class T1>
 A(T1) { }
};

A(1); // ok
A{1}; // not ok!

If you, or anyone, does see some language in the standard that clearly
supports the above deduction via A{1} (it appears that this can only
be interpreted as an undeduced context) - I would appreciate it being
pointed out to me.



>
> To me, the asymmetry is between template type deduction and auto type
> deduction.
>   I don't understand why
>
>    auto x = { 1, 2, 3 };
>
> deduces x's type to be a std::initializer_list<int>, but
>
>    template<typename T>
>    void f(T x);
>
>    f({1, 2, 3});
>
> won't deduce T to be std::initializer_list<int>.

It is not clear to me why this asymmetry exists either.

>
> > It would be convenient to be able to write:
>
> >  template<class T, int N> make_array(T (&a)[N])
> >  { return array<T,N>(a, a+N); }
>
> >  auto a = make_array({1,2,3}}; // Not ok!
>
> >  This is especially suprising because the accepted (albeit non-
> > normative)
> >  rewrite rule for initializer lists is that they can be written as
> > arrays.
> >  Yet you cannot initialize a reference to an array with it directly.
>
> It sounds like what you'd like is for essentially the same rules to
> apply during
> template argument deduction given a brace initializer argument as currently
> apply during initialization of objects of a known type.  I don't think
> that's an
> unreasonable desire, but I don't know if there are technical issues that would
> make it untenable, and at any rate, it seems like the committee has gone in a
> different direction.

Yes, it has, for now.  But perhaps if enough of us find this useful,
we can steer them in a different direction for C++1x ;)


thanks,
Faisal Vali
Radiation Oncology
Loyola


--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Scott Meyers <usenet@aristeia.com>
Date: Wed, 11 Nov 2009 17:22:32 CST
Raw View
Faisal Vali wrote:
>
> But I find nothing in the language of the standard that supports the
> following type deduction:
>
> struct A {
>  template<class T1>
>  A(T1) { }
> };
>
> A(1); // ok
> A{1}; // not ok!
>
> If you, or anyone, does see some language in the standard that clearly
> supports the above deduction via A{1} (it appears that this can only
> be interpreted as an undeduced context) - I would appreciate it being
> pointed out to me.

Because A has no constructor taking a std::initializer_list object,
13.3.1.7 says that A{1} is the same as A(1).  At least that's my
reading.

FWIW, gcc 4.4.1 compiles the above.

Scott

--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Johannes Schaub (litb)" <schaub-johannes@web.de>
Date: Wed, 11 Nov 2009 17:20:35 CST
Raw View
Faisal Vali wrote:

> On Nov 10, 5:01 pm, Scott Meyers <use...@aristeia.com> wrote:
>> My conclusion about brace initializer lists is that they are interpreted
>> as initializers for std::initializer_list objects if at all possible.
>> Only if this
>> interpretation is not possible does the compiler break them into their
>> constituent parts and look for a constructor taking that many arguments.
>> That
>> is, the "right and intuitive thing" is to interpret { a, b, c }  as an
>> initializer for a std::initializer_list<T> object, and only if there is
>> no std::initializer_list object to try to match is a constructor taking
>> some other type considered.
>
> This is my conclusion too.
>
> But I find nothing in the language of the standard that supports the
> following type deduction:
>
> struct A {
>  template<class T1>
>  A(T1) { }
> };
>
> A(1); // ok
> A{1}; // not ok!
>
> If you, or anyone, does see some language in the standard that clearly
> supports the above deduction via A{1} (it appears that this can only
> be interpreted as an undeduced context) - I would appreciate it being
> pointed out to me.
>

8.5 has the following to say:

    If the destination type is a reference type, see 8.5.3.
...
    If the initializer is a braced-init-list, the object is list-initialized
(8.5.4).

There are two things i wouldn't be 100% sure about when reading that without
having an implementation to check against:

#1 If the destination type is a reference, and the initializeris a braced-
init-list, what path to choose?
#2 If the initializer is "= { ... }" or "({ ... })", is it a "braced-init-
list" then?

The intent is that in #1, the path goes to 8.5.4, i think (since it handles
reference cases), and in #2, the intent is that "= { ... }" is list-
initialization, but "({ ... })" is not. "{ ... }" is direct-list-
initialization, and "= { ... }" is copy-list-initialization, while "({ ...
})" does not appear anywhere in an example of 8.5.4 - it seems to take the
path of non-list direct-initialization.

In your case, 5.2.3 says a temporary object is direct-list-initialized. It
is identical to

A a{1};

8.5.4/3 is entered, and it takes:

"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)."

So, your "T" is *not* deduced against "{1}", but just against "1". If you
use a double-brace, things are different:

struct A {
 template<typename T> A(T);
 A(A&); // so it doesn't go "copy-ctor -> A(int)"
};

A a{{1}}; // woops, T is non-deduced

In that case, GCC4.5 spits a warning about that it tricks, and that you can
use "-fno-deduce-init-list" to make it behave, and then it says that no
constructor is found.

---

If you look about initializing references to arrays, then you have the
problem that an array whose element types are qualified isn't itself
qualified (i think the state of this claim is very unsure though, if I look
at 8.3.4/1 the last note, and then 3.9.3/3 and 3.9.3/5, i'm completely lost
what's the state of that - also, can an rvalue array have type "int
const[5]"? EDG says no). In any case, i don't see something wrong when using
rvalue references:

// should work fine i think - doesn't work with GCC currently (4.5)
int (&&arr)[5] = { 1, 2, 3, 4, 5 };

The path goes from 8.5 to 8.5.4 then to 8.5.4/3b4, and then an rvalue array
is list-initialized, and bound by the rvalue reference. Have i overlooked
something?

--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Scott Meyers <usenet@aristeia.com>
Date: Mon, 2 Nov 2009 11:56:59 CST
Raw View
Scott Meyers wrote:
> What type do templates deduce for a brace initialization list?  Consider:
>
>    struct Widget {
>      template<typename T>
>      Widget(T);
>    };
>
>    Widget w { 1, 2, 3 };  // compiles?  if so, what is T?

Following up on my own post, I think this should not compile per 14.9.2.1/1,
which says that brace initializer lists prevent type deduction except in cases
where the corresponding parameter for the template function is of type
std::initializer_list.  This example is provided:

   template<class T> void g(T);
   g({1,2,3}); // error: no argument deduced for T

> gcc 4.4 rejects the code, but it takes this version:
>
>    struct Widget {
>      template<typename T>
>      Widget(T, T, T);
>    };
>
>    Widget w { 1, 2, 3 };  // compiles under gcc 4.4
>
> This suggests that per 8.5.4/3, a brace initialization list used with a
> class
> type either matches a constructor taking a std::initializer_list object
> or it
> matches a constructor that can be called with as many arguments as are
> in the
> brace initialization list.  gcc's behavior also suggests that either of
> these
> constructors may be generated from templatized constructors.
>
> Interestingly, this seems to be a difference from how brace
> initialization lists
> are treated with auto.  Given
>
>    auto x = { 1, 2, 3 };
>
> gcc deduces x's type as std::initializer_list<int>, which seems
> consistent with
> 7.1.6.4/6.  That suggests that a difference between type deduction for
> auto and
> for template parameters is that during deduction for auto, a brace
> initialization list is automatically turned into a std::inititalizer_list
> object, but during type deduction for a template, a brace initialization
> list is
> not automatically turned into a std::initialization_list.

This seems consistent with 14.9.2.1/1.

> If this is correct (that there's no way to pass a brace initialization
> list to a
> template and have a type for it deduced), this implies that there is no
> way to
> perfectly forward a brace initialization list.

Again, 14.9.2.1/1 seems to imply this.

Scott


--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Master T <tom.deseyn@gmail.com>
Date: Tue, 3 Nov 2009 12:26:59 CST
Raw View
On 2 nov, 18:56, Scott Meyers <use...@aristeia.com> wrote:
> Scott Meyers wrote:
> > What type do templates deduce for a brace initialization list?  Consider:
>
> >    struct Widget {
> >      template<typename T>
> >      Widget(T);
> >    };
>
> >    Widget w { 1, 2, 3 };  // compiles?  if so, what is T?
>
> Following up on my own post, I think this should not compile per 14.9.2.1/1,
> which says that brace initializer lists prevent type deduction except in cases
> where the corresponding parameter for the template function is of type
> std::initializer_list.  This example is provided:
>
>    template<class T> void g(T);
>    g({1,2,3}); // error: no argument deduced for T
>
>
>
>
>
> > gcc 4.4 rejects the code, but it takes this version:
>
> >    struct Widget {
> >      template<typename T>
> >      Widget(T, T, T);
> >    };
>
> >    Widget w { 1, 2, 3 };  // compiles under gcc 4.4
>
> > This suggests that per 8.5.4/3, a brace initialization list used with a
> > class
> > type either matches a constructor taking a std::initializer_list object
> > or it
> > matches a constructor that can be called with as many arguments as are
> > in the
> > brace initialization list.  gcc's behavior also suggests that either of
> > these
> > constructors may be generated from templatized constructors.
>
> > Interestingly, this seems to be a difference from how brace
> > initialization lists
> > are treated with auto.  Given
>
> >    auto x = { 1, 2, 3 };
>
> > gcc deduces x's type as std::initializer_list<int>, which seems
> > consistent with
> > 7.1.6.4/6.  That suggests that a difference between type deduction for
> > auto and
> > for template parameters is that during deduction for auto, a brace
> > initialization list is automatically turned into a std::inititalizer_list
> > object, but during type deduction for a template, a brace initialization
> > list is
> > not automatically turned into a std::initialization_list.
>
> This seems consistent with 14.9.2.1/1.
>
> > If this is correct (that there's no way to pass a brace initialization
> > list to a
> > template and have a type for it deduced), this implies that there is no
> > way to
> > perfectly forward a brace initialization list.
>
> Again, 14.9.2.1/1 seems to imply this.
>
> Scott
>
> --
> [ comp.std.c++ is moderated.  To submit articles, try just posting with ]
> [ your news-reader.  If that fails, use mailto:std-...@netlab.cs.rpi.edu]
> [              --- Please see the FAQ before posting. ---               ]
> [ FAQ:http://www.comeaucomputing.com/csc/faq.html                     ]

The elements of an initializer_list are put on an implementation
generated array.
rvalue/lvalueness will be taken into account when this array is
generated.
The elements are accessible only via const references.

It isn't possible to perfect forward (or even reference the original
elements) that generate the initializer_list.


--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Faisal Vali <faisalv@gmail.com>
Date: Thu, 5 Nov 2009 16:28:23 CST
Raw View
On Nov 2, 11:56 am, Scott Meyers <use...@aristeia.com> wrote:
> Scott Meyers wrote:
> > What type do templates deduce for a brace initialization list?  Consider:
>
> >    struct Widget {
> >      template<typename T>
> >      Widget(T);
> >    };
>
> >    Widget w { 1, 2, 3 };  // compiles?  if so, what is T?
>
> Following up on my own post, I think this should not compile per 14.9.2.1/1,

Agreed.

<snip and rearrange>
>
> > gcc 4.4 rejects the [above] code, but it takes this version:
>
> >    struct Widget {
> >      template<typename T>
> >      Widget(T, T, T);
> >    };
>
> >    Widget w { 1, 2, 3 };  // compiles under gcc 4.4
>
> > This suggests that per 8.5.4/3, a brace initialization list used with a
> > class
> > type either matches a constructor taking a std::initializer_list object
> > or it
> > matches a constructor that can be called with as many arguments as are
> > in the
> > brace initialization list.  gcc's behavior also suggests that either of
> > these
> > constructors may be generated from templatized constructors.

Yes - but it is not clear to me what portion of the standard
prescribes gcc
4.4's behavior here in allowing that template constructor to work.

I thought that the following code would not work with list-
initialization per
the standard (my variadic syntax might be a little off):

struct Widget {
 template<class T>
 Widget(T&&  t)  { std:: cout << t << std::endl; }
 template<class First, class ... Rest>
 Widget(First&&  f, Rest&& ... r) : Widget(r...)
 { std::cout << f << std::endl; }
};

Widget w{1,2,3};  // not ok,

Widget w(1,2,3); // ok

If the list-initialization case is ill-formed, I think that is
especially
unfortunate since list-initialization is supposed to do the right and
intuitive
thing each time (and minimize asymmetry) - but in this case it
introduces
asymmetry (with template constructors).  And if that is not the case,
can
anyone please point to the wording in the standard that allows it?


> brace initializer lists prevent type deduction except in cases
> where the corresponding parameter for the template function is of type
> std::initializer_list.

I find this to be somewhat unfortunate too.

It would be convenient to be able to write:

 template<class T, int N> make_array(T (&a)[N])
 { return array<T,N>(a, a+N); }

 auto a = make_array({1,2,3}}; // Not ok!

 This is especially suprising because the accepted (albeit non-
normative)
 rewrite rule for initializer lists is that they can be written as
arrays.
 Yet you cannot initialize a reference to an array with it directly.

 int i = { 3 }; // ok
 const int &i = {3}; // ok.

 int arr[5] = { 2, 3, 4 }; // ok.
 const int (&arr)[5] = { 2, 3, 4 };  // Not ok!

 Also, now that initializer lists can be used in return-statements, is
there a
 good reason for not allowing functions to return arrays of known
sizes?

 int (f())[10] { return { 1, 2, 3 }; }

While I feel initializer lists make certain very important use cases
already
easier and more intuitive - it would have been even better if we could
have
included the above ones too.

Any thoughts?

regards,
Faisal Vali
Radiation Oncology
Loyola




--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Scott Meyers <usenet@aristeia.com>
Date: Sat, 31 Oct 2009 22:07:15 CST
Raw View
What type do templates deduce for a brace initialization list?  Consider:

    struct Widget {
      template<typename T>
      Widget(T);
    };

    Widget w { 1, 2, 3 };  // compiles?  if so, what is T?

gcc 4.4 rejects the code, but it takes this version:

    struct Widget {
      template<typename T>
      Widget(T, T, T);
    };

    Widget w { 1, 2, 3 };  // compiles under gcc 4.4

This suggests that per 8.5.4/3, a brace initialization list used with a class
type either matches a constructor taking a std::initializer_list object or it
matches a constructor that can be called with as many arguments as are in the
brace initialization list.  gcc's behavior also suggests that either of these
constructors may be generated from templatized constructors.

Interestingly, this seems to be a difference from how brace initialization lists
are treated with auto.  Given

    auto x = { 1, 2, 3 };

gcc deduces x's type as std::initializer_list<int>, which seems consistent with
7.1.6.4/6.  That suggests that a difference between type deduction for auto and
for template parameters is that during deduction for auto, a brace
initialization list is automatically turned into a std::inititalizer_list
object, but during type deduction for a template, a brace initialization list is
not automatically turned into a std::initialization_list.

If this is correct (that there's no way to pass a brace initialization list to a
template and have a type for it deduced), this implies that there is no way to
perfectly forward a brace initialization list.  The following experiment with
gcc 4.4 is consistent with this conclusion:

    #include <utility>
    #include <iostream>

    class Widget {
    public:
      Widget(int x, int y, int z)
      { std::cout << x << y << z << '\n'; }
    };

    template<typename ... Args>
    void makeWidget(Args&&... args)
    {
      Widget w(std::forward<Args>(args)...);
    }

    int main()
    {
      Widget w({1, 2, 3});    // works: ctor call with brace init list
      makeWidget(1, 2, 3);    // works: fwded call with brace list contents
      makeWidget({1, 2, 3});  // error! fwded call with brace init list
    }

Have I misunderstood something somewhere?

Thanks,

Scott


--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Master T <tom.deseyn@gmail.com>
Date: Sun, 1 Nov 2009 21:26:58 CST
Raw View
On 1 nov, 05:07, Scott Meyers <use...@aristeia.com> wrote:
> What type do templates deduce for a brace initialization list?  Consider:
>
>     struct Widget {
>       template<typename T>
>       Widget(T);
>     };
>
>     Widget w { 1, 2, 3 };  // compiles?  if so, what is T?
>
> gcc 4.4 rejects the code, but it takes this version:
>
>     struct Widget {
>       template<typename T>
>       Widget(T, T, T);
>     };
>
>     Widget w { 1, 2, 3 };  // compiles under gcc 4.4
>
> This suggests that per 8.5.4/3, a brace initialization list used with a class
> type either matches a constructor taking a std::initializer_list object or it
> matches a constructor that can be called with as many arguments as are in the
> brace initialization list.  gcc's behavior also suggests that either of these
> constructors may be generated from templatized constructors.
>
> Interestingly, this seems to be a difference from how brace initialization lists
> are treated with auto.  Given
>
>     auto x = { 1, 2, 3 };
>
> gcc deduces x's type as std::initializer_list<int>, which seems consistent with
> 7.1.6.4/6.  That suggests that a difference between type deduction for auto and
> for template parameters is that during deduction for auto, a brace
> initialization list is automatically turned into a std::inititalizer_list
> object, but during type deduction for a template, a brace initialization list is
> not automatically turned into a std::initialization_list.
>
> If this is correct (that there's no way to pass a brace initialization list to a
> template and have a type for it deduced), this implies that there is no way to
> perfectly forward a brace initialization list.  The following experiment with
> gcc 4.4 is consistent with this conclusion:
>
>     #include <utility>
>     #include <iostream>
>
>     class Widget {
>     public:
>       Widget(int x, int y, int z)
>       { std::cout << x << y << z << '\n'; }
>     };
>
>     template<typename ... Args>
>     void makeWidget(Args&&... args)
>     {
>       Widget w(std::forward<Args>(args)...);
>     }
>
>     int main()
>     {
>       Widget w({1, 2, 3});    // works: ctor call with brace init list
>       makeWidget(1, 2, 3);    // works: fwded call with brace list contents
>       makeWidget({1, 2, 3});  // error! fwded call with brace init list
>     }
>
> Have I misunderstood something somewhere?
>
> Thanks,
>
> Scott
>
> --
> [ comp.std.c++ is moderated.  To submit articles, try just posting with ]
> [ your news-reader.  If that fails, use mailto:std-...@netlab.cs.rpi.edu]
> [              --- Please see the FAQ before posting. ---               ]
> [ FAQ:http://www.comeaucomputing.com/csc/faq.html                     ]

To have a template initializer list, you must explicitly mention the
initializer_list:
template<typename T>
void foo(initializer_list<T>);

A general parameter T will never be deduced as an initializer_list

The handling of auto is different: it will create an initializer_list
from a brace enclosed list.

For more info: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2640.pdf
(especially page 11)

Kind Regards,

Tom


--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]