Topic: Type of initializer lists ( N2215 feedback)
Author: "Vidar Hasfjord" <vattilah-groups@yahoo.co.uk>
Date: Fri, 13 Apr 2007 17:02:53 CST Raw View
On Mar 26, 3:40 pm, "Anders Dalvander" <goo...@dalvander.com> wrote:
> Could something like this be used?
>
> auto a = std::make_array({/*...*/});
Yes, it seems so. I argued in earlier posts that it wasn't possible to
deduce the array size, but let me correct myself. After further study
of the interaction of the variadic templates proposal and initializer
lists proposal (N2080 and N2215), I come to the conclusion that it is
indeed possible. Here's how:
- An initializer list (IL) can initialize an argument.
- The IL will unpack into a constructor argument list.
- The matching constructor can be a variadic template.
- A variadic template uses a new entity called "parameter pack".
- A parameter pack captures the number and types of arguments.
So make_array can be declared like this (I leave it as an exercise to
implement it):
template <typename... Args>
array <typename common_type <Args...>::type, sizeof Args>
make_array (tuple <Args...>);
This demonstrates that the full type of an initializer list (i.e. the
number and types of the elements) can be captured and processed by the
language. Hence I guess I can allay my primary concern about the
initializer lists proposal.
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "Vidar Hasfjord" <vattilah-groups@yahoo.co.uk>
Date: Sun, 15 Apr 2007 22:51:58 CST Raw View
On Apr 14, 12:02 am, "Vidar Hasfjord" <vattilah-gro...@yahoo.co.uk>
wrote:
> This demonstrates that the full type of an initializer list (i.e. the
> number and types of the elements) can be captured and processed by the
> language. Hence I guess I can allay my primary concern about the
> initializer lists proposal.
Scratch that. My primary fear about the initializer list proposal is
back in full force!
I suggested:
template <typename... Args>
array <typename common_type <Args...>::type, sizeof Args>
make_array (tuple <Args...>);
The problem with the above is that the tuple type cannot be deduced
from the initalizer list argument (it would require deduction of class
template arguments based on constructor arguments, which is not in the
current language nor planned for C++09, AFAIK). Hence the user must
specify the type of the tuple to use the function. For example:
auto a = make_array (tuple <int, int> {1, 2});
Which makes the factory function useless.
So I'm back were I started. I can't see a way to directly capture the
size and element types (i.e. all the type info) of an initializer
list.
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "Vidar Hasfjord" <vattilah-groups@yahoo.co.uk>
Date: Mon, 16 Apr 2007 08:44:28 CST Raw View
On Apr 16, 5:51 am, "Vidar Hasfjord" <vattilah-gro...@yahoo.co.uk>
wrote:
> So I'm back were I started. I can't see a way to directly capture the
> size and element types (i.e. all the type info) of an initializer list.
Well, I think I've figured out part of the problem now; the deduction
of the size of a homogeneous initializer list. Sorry for the long
journey.
The size of a homogeneous initializer list can be captured via a
conversion to an array of implicit size:
auto a = make_array (int [] {1, 2, /*...*/});
Here the element type is specified explicitly. Provided that the auto
keyword will be allowed in an initializer the element type can be
implicit as well:
auto a = make_array (auto [] {1, 2, /*...*/});
Here is a possible implementation of make_array:
template <typename T, size_t N>
array <T, N> make_array (T (&a) [N]) {
return array <T, N> (a);
}
While this solves the size deduction problem for a homogeneous
initializer list, the solution is still more verbose than the variadic
template alternative:
auto a = make_array (1, 2, /*...*/);
Unfortunately this defeats the goal to make initializer lists a
preferred "notation for all initialization" (N2215).
In general, a variadic template has the power to determine and process
both the number and types of the arguments passed. This happens at
compile time, which makes variadic templates well suited for
initialization. The only drawback of variadic templates I can see is
that (some) compilers may run into a capacity problem for long
argument lists due to the recursive nature of variadic template
instantiation.
Sidebar comment: A variadic template's parameter pack can be viewed as
C++09's built-in tuple descriptor. It contains full type and size info
for an argument list. But it is not a type, and it is processed using
entirely new syntactic and semantic language constructs. Intuitively
it seems possible to combine this concept of a tuple with the current
library version and apply the resulting type to both argument lists
and initializer lists --- but I guess that would have far-reaching
implications for the language.
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Richard Smith <richard@ex-parrot.com>
Date: Wed, 18 Apr 2007 08:29:35 CST Raw View
Vidar Hasfjord wrote:
> On Apr 1, 9:09 pm, "Mathias Gaunard" <loufo...@gmail.com> wrote:
>
>>You could very well do auto a = std::make_array( { /* ... */ } ); if
>>the lists carries the size information.
>
>
> Which is exactly my point. Unfortunately the initializer_list type as
> proposed in N2215 does not carry the size.
>
> (The value does however carry size as a constexpr which can be
> evaluated at compile time. But can you transport it from the value
> domain to the type domain? I don't see how.)
If were a true constexpr, this would be easy. For example:
template <class Init>
std::array< typename Init::value_type, Init::size() >
make_array( Init&& init ) {
std::array< typename Init::value_type, Init::size() > rv = init;
return rv;
}
But as I understand it, size() isn't a proper constexpr -- see 4.5.7 in
n2215. What's more, where there is only one instantiation of make_array
per type of Init, and the value of the initalizer_list's size isn't
folded into its type, even if some compiler magic can make it work when
it's first instantiated, the fact that the same instantiation is shared
between different sized initalizer_lists, breaks this.
Stroustrup gave an interesting talk on this subject at the ACCU
conference last week. He mentioned, while discussing alternative
syntaxes that had been suggested, that they had considered using
template <class T, std::size_t N>
foo::foo( T (&)[N] ) {}
as the syntax for the sequence constructor, but had rejected it
precisely because it folded the size, N, into the type system.
This could probably be solved in the same way that template argument
deduction works for built-in arrays -- via type decay. And we could do
this via inheritance in the library:
template <class T, std::size_t N>
struct sized_initializer_list : initalizer_list<T> {
typedef std::size_t size_type;
constexpr size_type size() { return N; }
};
(and remove the 'constexpr' keyword from the base class size() method as
it isn't really true.)
Then we define the type of {1,2,3} to be sized_initializer_list<int,3>
and it all works. (By virtue of the last bullet point of 14.8.2.1/3,
the derived-to-base conversion will occur during template argument
deduction for a parameter of the form initializer_list<T>.)
This way, when we know that we don't care about the size of the array
(for example in a vector sequence constructor), we can drop it from the
type system and not instantiate one constructor per initializer list
size. And when we do need it (for example, with a make_array function),
we can still have it. And in the case of a general forwarding function:
template <typename T, typename... Args>
std::unique_ptr<T>
safe_new( Args&&... args ) {
return std::unique_ptr<T>
( new T( std::forward<Args>(args)... );
}
(excuse an errors in the variadic function syntax), the derived-to-base
conversion will *not* happen and our initializer list gets forwarded
complete with size.
Having said that, yes, I would like to see support for heterogenous
initializer lists, probably using some form tuple-like type. I would
imagine that with some concept trickery, this could be made to work
nicely: heterogenous initializer lists would be std::tuples, homogenous
initializer lists would be std::initializer_lists and by judicious use
of concepts we shouldn't care which we've been given. Perhaps.
--
Richard Smith
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "Vidar Hasfjord" <vattilah-groups@yahoo.co.uk>
Date: Tue, 10 Apr 2007 18:18:33 CST Raw View
On Apr 1, 9:09 pm, "Mathias Gaunard" <loufo...@gmail.com> wrote:
> You could very well do auto a = std::make_array( { /* ... */ } ); if
> the lists carries the size information.
Which is exactly my point. Unfortunately the initializer_list type as
proposed in N2215 does not carry the size.
(The value does however carry size as a constexpr which can be
evaluated at compile time. But can you transport it from the value
domain to the type domain? I don't see how.)
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "Vidar Hasfjord" <vattilah-groups@yahoo.co.uk>
Date: Tue, 10 Apr 2007 18:22:35 CST Raw View
On Apr 10, 6:20 pm, yechez...@emailaccount.com (Yechezkel Mett) wrote:
> Both these cases can be solved by using variadic templates.
This is my observation as well. Variadic templates and tuples provide
a general orthogonal solution.
> In general, where the type can vary initializer lists are not the right solution.
Well, I don't see why. Had the initializer syntax only applied to
homogenous lists I would have been more inclined to agree, but
initializer lists are already in use for structs (a tuple in
principle), and the proposal is to generalize them for any user
defined constructor (a constructor, well any function, argument list
is in principle a tuple).
With variadic templates the type system of the language will be
powerful enough to fully describe the type of an initializer list as a
tuple <T1, T2, ..., Tn>. Such a type carries both size and exact
element type info and can be processed by meta-programming into any
other type, including a homogenous initializer_list.
Hence it would seem like a lost opportunity for simplicity and
orthogonality in the language to formulate the semantics of a
initializer list just as a series of pragmatic initialization rules
and an occasional transformation into the limited initializer_list
type; like the current proposal does.
A solution based on tuples would need implicit conversions and other
language rules to describe currently allowed constructs, and as
always, the devil is in the detail. But it seems feasible. I touch
briefly on these issues in an earlier post:
http://groups.google.com/group/comp.lang.c++.moderated/msg/bd212a211a7fb888
I welcome any comments on the problems with this approach.
Here is an off-the-cuff attempt at some simple rules:
A. Consider: {v1, v2, ..., vn}
1. The type is tuple <T1, T2, ..., Tn>.
2. Can match a constructor or function argument list (with implicit
conv.).
3. Convertible to T [N], if for all Ti in <T1...Tn>, Ti is
convertible to T.
4. Convertible to initializer_list <T> if (3).
B. Consider: {}
1. The type is null_tuple.
2. Can match an empty constructor or function argument list.
3. Convertible to initializer_list <T> for any T.
Application:
int v = {}; // int i = int (); // B2
int v = {1}; // int i = int (1); // A2
int a [] = {}; // error: T [0] not allowed
int a [] = {1]; // int a [] = static_cast <int [1]> ({1}); // A3
struct S {int i; string s;};
S s = {}; // S s = S(); // B2
S s = {1, "A"}; // S s = S(1, "A"); // A2 (*)
typedef vector <int> V;
V v = {}; // V v (); // A1
V v = {100, 1}; // V v (100, 1); // A2 (**)
V v = {1, 2, 3}; // V v (initializer_list <int> ({1, 2, 3}); // A4
template <class T> f (T);
f ({1}); // f (tuple <int> (1)); // A1
f ({}); // f (null_tuple ()); // B1
(*) This case requires a new language rule that adds an implicit
constructor to any struct that doesn't provide one itself. Then define
the meaning of S s = {1, "A"} as equivalent to S s (1, "A"), making
this case just an ordinary application of the rules and not a special
case.
(**) This case is inconvenient, it calls vector (size_t, T). We'd want
{1, 2} to be considered a list, because that's the intuitive meaning
for a vector. This can be solved by adding a templated constructor for
vector that takes a tuple and forwards to the intializer_list
constructor, i.e. it is explicitly disambiguated in code. Nice!
Another solution is to tag vector (size_t, T) as an explicit
constructor and not allow tuples to be matched with explicit
constructors. This follows naturally from other language rules when
one considers initalizer lists as objects of a defined type (tuple).
Nice extension:
C. Consider: struct S {T1 v1; ...; Tn vn;};
1. If a POD, S has the base type tuple <T1, ..., Tn>.
Application:
template <class T1, ...>
void f (tuple <T1, ...> v);
f ({1, 'a'}); // f (tuple <int, char> (1, 'a')); // A1
struct S {int i; char c;} s = {1, 'a'};
f (s); // f (tuple <int, char> (s.i, s.c)); // C1
(By the way, let me correct myself: In the original post I claimed
there was type decay when passing f({0x0D, 3.14}) where f is a
template <class T> f (T). This is not correct. The proposal, N2215,
wont allow the call at all.)
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "Mathias Gaunard" <loufoque@gmail.com>
Date: Wed, 11 Apr 2007 12:47:57 CST Raw View
On Mar 26, 7:36 am, "Vidar Hasfjord" <vattilah-gro...@yahoo.co.uk>
wrote:
> I regularly read the latest papers and proposals for C++09 with great
> interest, and I commend everyone who is contributing so much time and
> hard work to improve the language. One of the new features I welcome
> from a language usability perspective is the generalization of
> initializer lists (N2215).
>
> That said, the current proposed solution seems less than optimally
> orthogonal.
I hadn't read it before, but now that I've done it looks rather bad.
I was expecting something more along the lines of
boost::mpl::vector_c, but since C99-like initialization is wanted and
not just initialization from constant expressions, that's impossible.
So the solution given in the paper is just to make the
initializer_list a container of values (that isn't even heterogenous
it seems) and traverse it at runtime.
Not really an efficient solution. Actually using an initializer_list
to initialize an array would be way less efficient that initialize it
with regular initialization lists, because lots of copies would be
needed.
The standard C++ library already has too much useless copies, I'm not
sure of the worth to add even more.
But actually, is a value really what initializes things? I would
rather say that an initializer is a function that indicates how to
construct the objects.
As a result, wouldn't it be better to actually to make those
initializer_lists into a list of functors? Thanks to that,
construction will be delayed, there will be no useless copy, since
creating the objects will be delegated to the object actually using
the initializer_list to initialize itself.
Thanks to inlining technology, it would be as efficient as the real
thing, in-place construction.
For example, here is what it could actually do :
struct Foo
{
Foo() {}
Foo(int) {}
};
int i = 7;
std::vector<Foo> foo = { 3, Foo(i), Foo() };
would be transformed to
struct lambda_1
{
typedef int type;
void operator()(void* p)
{
new(p) int(3);
}
};
struct lambda_2
{
typedef Foo type;
lambda_2(int&& a) : a_(a) {}
void operator()(void* p)
{
new(p) Foo(a_);
}
private:
a&& a_;
};
struct lambda_3
{
typedef Foo type;
void operator()(void* p)
{
new(p) Foo();
}
};
std::vector<Foo> foo(std::make_tuple(lambda_1(), lambda_2(i),
lambda_3()));
Of course, the constructor to initializes from that kind of stuff
becomes quite less trivial than with iterators, but it happens at
compile-time, there is no copy, and the initializer-list can be
heterogenous. It allows the same kind of thing as regular C99
initialization lists, except that /we/ choose what to do with the
list.
Here, depending on whether lambda_x::type is Foo, we either directly
call the the functor on the memory or we create a temporary of type
lambda_x::type, initialize it with the functor, and construct a Foo in
memory with it.
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: yechezkel@emailaccount.com (Yechezkel Mett)
Date: Tue, 10 Apr 2007 17:20:42 GMT Raw View
Vidar Hasfjord wrote:
> I regularly read the latest papers and proposals for C++09 with great
> interest, and I commend everyone who is contributing so much time and
> hard work to improve the language. One of the new features I welcome
> from a language usability perspective is the generalization of
> initializer lists (N2215).
>
> That said, the current proposed solution seems less than optimally
> orthogonal. For example, C++ currently allows the following
> initialization:
>
> int a [] = {/*...*/}; // The size is inferred.
>
> As is well known, C arrays are flawed (due to type decay) and a new
> library type has been introduced to replace its use (std::tr1::array).
> Unfortunately the language grants the above special feature to built-
> in arrays which is not available to a user-defined type:
>
> array <T, ?> a = {/*...*/}; // The size can not be inferred.
>
> The proposed intializer_type does nothing to improve this situation as
> it does not carry size in the type info, hence it does not support any
> size inference. You are forced to do a cumbersome (and inefficient)
> rewrite, something the proposal set out to eliminate in the first
> place. For example:
>
> initializer_list <T> il = {/*...*/};
> array <T, il.size ()> a = il; // il.size () is a constexpr.
>
> Further, the proposed treatment of initializer lists also suffers from
> type decay. Consider the following construct:
>
> template <class T> f (T);
> f (make_tuple (0x0D, 3.14)); // T = tuple <int, double>
>
> With generalized initializer lists you can do:
>
> f ({0x0D, 3.14}); // T = initializer_list <double>
>
> But, as I understand the proposal, the (undefined) type of the
> initializer list decays to initializer_list <double>, causing loss of
> type info.
Both these cases can be solved by using variadic templates. In general,
where the type can vary initializer lists are not the right solution.
Yechezkel Mett
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "Vidar Hasfjord" <vattilah-groups@yahoo.co.uk>
Date: Sat, 31 Mar 2007 16:38:00 CST Raw View
On Mar 26, 3:40 pm, "Anders Dalvander" <goo...@dalvander.com> wrote:
> Could something like this be used?
>
> auto a = std::make_array({/*...*/});
No. You need varying function argument signature to produce varying
function result type. The problem is that the initializer_list, as
proposed in N2215, does not carry size info in the type (like built-in
arrays do). Hence your make_array will always produce the same type of
result for all initializer lists of same element type, irrespective of
size. You could do:
auto a = std::make_array <...put the size here...> ({/*...*/});
But then you're back where you started.
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "Mathias Gaunard" <loufoque@gmail.com>
Date: Sun, 1 Apr 2007 14:09:57 CST Raw View
On Apr 1, 12:38 am, "Vidar Hasfjord" <vattilah-gro...@yahoo.co.uk>
wrote:
> On Mar 26, 3:40 pm, "Anders Dalvander" <goo...@dalvander.com> wrote:
> > auto a = std::make_array({/*...*/});
>
> No. You need varying function argument signature to produce varying
> function result type. The problem is that the initializer_list, as
> proposed in N2215, does not carry size info in the type (like built-in
> arrays do). Hence your make_array will always produce the same type of
> result for all initializer lists of same element type, irrespective of
> size. You could do:
>
> auto a = std::make_array <...put the size here...> ({/*...*/});
>
> But then you're back where you started.
You could very well do auto a = std::make_array( { /* ... */ } ); if
the lists carries the size information.
I don't know much about initializer lists, but it would be something
like
template<typename L>
array<typename L::element_type, L::size> make_array(L&& l)
{
return array<typename L::element_type, L::size>(L);
}
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "Vidar Hasfjord" <vattilah-groups@yahoo.co.uk>
Date: Sun, 25 Mar 2007 23:36:35 CST Raw View
I regularly read the latest papers and proposals for C++09 with great
interest, and I commend everyone who is contributing so much time and
hard work to improve the language. One of the new features I welcome
from a language usability perspective is the generalization of
initializer lists (N2215).
That said, the current proposed solution seems less than optimally
orthogonal. For example, C++ currently allows the following
initialization:
int a [] = {/*...*/}; // The size is inferred.
As is well known, C arrays are flawed (due to type decay) and a new
library type has been introduced to replace its use (std::tr1::array).
Unfortunately the language grants the above special feature to built-
in arrays which is not available to a user-defined type:
array <T, ?> a = {/*...*/}; // The size can not be inferred.
The proposed intializer_type does nothing to improve this situation as
it does not carry size in the type info, hence it does not support any
size inference. You are forced to do a cumbersome (and inefficient)
rewrite, something the proposal set out to eliminate in the first
place. For example:
initializer_list <T> il = {/*...*/};
array <T, il.size ()> a = il; // il.size () is a constexpr.
Further, the proposed treatment of initializer lists also suffers from
type decay. Consider the following construct:
template <class T> f (T);
f (make_tuple (0x0D, 3.14)); // T = tuple <int, double>
With generalized initializer lists you can do:
f ({0x0D, 3.14}); // T = initializer_list <double>
But, as I understand the proposal, the (undefined) type of the
initializer list decays to initializer_list <double>, causing loss of
type info.
The proposal restricts use of heterogeneous initializer lists in
argument passing to non-templated parameters of known type that either
is a compatible POD struct or has a compatible constructor. For
example,
struct S {int, string};
class C {public: C (int, string); /*...*/};
void f (S);
void g (C);
template <class T> h (T);
void f ({42, "a"}); // ok
void g ({42, "a"}); // ok
void h ({42, "a"}); // error; no descriptive type exists.
The ideal solution would be to define a type for an initializer list
that is fully descriptive; tuple <T1, T2, ..., Tn> seems the obvious
choice; and to devise implicit conversions to the types necessary for
existing initialization constructs. Here is an exploratory proposal
that I wrote earlier:
http://groups.google.com/group/comp.lang.c++.moderated/msg/bd212a211a7fb888
Has this approach been exhaustively explored? If so, why was it
abandoned? Intuitively, it seems a built-in tuple type could form the
foundation for the proposed solution by supporting conversions to
initializer_list, hence overcome the related issues discussed in the
rationale section of the current proposal as well as the issues
discussed above.
Regards,
Vidar Hasfjord
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "Anders Dalvander" <google@dalvander.com>
Date: Mon, 26 Mar 2007 08:40:21 CST Raw View
On Mar 26, 7:36 am, "Vidar Hasfjord" <vattilah-gro...@yahoo.co.uk>
wrote:
> array <T, ?> a = {/*...*/}; // The size can not be inferred.
Could something like this be used?
auto a = std::make_array({/*...*/});
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]