Thanks for all illumination.
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 ]
Presumably, so that it can be statically initialized, in order
to avoid order of initialization problems. (It still needs a
mechanism for the number of elements to be determined,
statically, from the initialization list, in order to be truly
useful.)
--
James Kanze
Well, the size of the std::array is part of its type, so if dynamic
initialization were permitted, all we'd need would be a specification for what
happens if the number of clauses in the init list didn't match the declared size
of the std::array. To keep things semi-consistent with aggregate
initialization, we could say that std::array elements without initializers would
be value initialized, and if too many initializers were specified, an exception
would be thrown.
Actually, now that I think of it, I'm pretty sure std::array objects can be
dynamically initialized:
std::array<int, 3> a { f(), g(), h() }; // legal, no?
As for avoiding order of initialization problems, why should std::array be
special in this regard?
Scott
> On Dec 18, 9:00 pm, Scott Meyers <NeverR...@aristeia.com> wrote:
>> In TR1, std::tr1::array needed to be an aggregate so that it
>> could be brace-initialized. To offer the same capability for
>> C++0x's std::array, the class could simply declare a
>> constructor taking a std::initializer_list. So why is
>> std::array an aggregate? Making it a non-aggregate would
>> permit e.g., giving it a constructor taking a pair of
>> iterators.
>
> Presumably, so that it can be statically initialized, in order
> to avoid order of initialization problems.
>
I believe this can all be done using variadic templates:
template<typename T, size_t S>
struct array {
template<typename ...U>
constexpr array(U&&... u):elems{ u... } { }
T elems[S];
};
array<int, 3> a = { 1, 2 }; // works, is statically initialized
array<int, 2> a = { 1, 2, 3 }; // gives an error at compile time
It doesn't seem to be that we can use static_cast or std::forward here to
enable move semantics, as i think it will render the initializers to non-
potential-constant-expressions.
> Well, the size of the std::array is part of its type, so if
> dynamic initialization were permitted, all we'd need would be
> a specification for what happens if the number of clauses in
> the init list didn't match the declared size of the
> std::array. To keep things semi-consistent with aggregate
> initialization, we could say that std::array elements without
> initializers would be value initialized, and if too many
> initializers were specified, an exception would be thrown.
That still means you have to write the size. An exception isn't
as good as a compiler error, and a compiler error isn't as good
as not having to write the size to begin with.
> Actually, now that I think of it, I'm pretty sure std::array
> objects can be dynamically initialized:
> std::array<int, 3> a { f(), g(), h() }; // legal, no?
Of course they can. So can C style arrays:
int a[] = { f(), g(), h() };
> As for avoiding order of initialization problems, why should
> std::array be special in this regard?
What's special is that it can also be initialized statically:
std::array< int, 3 > a = { 1, 2, 3 };
Regardless of where this definition appears, I'm guaranteed that
it occurs before *any* user defined code, including code in
constructors of other static objects. In certain cases, that's
a useful guarantee.
--
James Kanze
I couldn't agree with you more - if only the following was allowed
with initializer lists, references to arrays, and aggregates:
template<class T, int N> std::array<T,N> array(const T (&arr)[N])
{
return arr;
}
auto a = array({1,2,3,4});
regards,
Faisal Vali
Radiation Oncology
Loyola
Alas, this won't work, because you can't deduce a template argument's
type from a brace initializer. The standardizion people have decided
that such a type can be deduced in the context of auto, but not a
template. I have no idea why.
Scott
Perhaps, but I don't see your point, because you have to manually
specify the size of a std::array if you initialize it with a brace
initializer list, regardless.
> What's special is that it can also be initialized statically:
>
> std::array< int, 3 > a = { 1, 2, 3 };
>
> Regardless of where this definition appears, I'm guaranteed that
> it occurs before *any* user defined code, including code in
> constructors of other static objects. In certain cases, that's
> a useful guarantee.
Is this really true? What about the following at namespace scope?
std::array<int, 3> a = { f(), g(), h() };
std::array<int, 3> b = { 1, 2, 3 };
Here's what draft C++0x has to say in 3.6.2/2, which I believe is
where we need to look:
> Objects with static storage duration (3.7.1) or thread storage duration
> (3.7.2) shall be zero-initialized (8.5) before any other initialization
> takes place.
> Constant initialization is performed:
>
> - if each full-expression (including implicit conversions) that appears in
> the initializer of a reference with static or thread storage duration is a
> constant expression (5.19) and the reference is bound to an lvalue
> designating an object with static storage duration or to a temporary (see
> 12.2)
>
> - if an object with static or thread storage duration is initialized such
> that the initialization satisfies the requirements for the object being
> declared with constexpr (7.1.5).
>
> Together, zero-initialization and constant initialization are called static
> initialization; all other initialization is dynamic initialization.
In the code I showed, b isn't a reference, so the first bullet doesn't
apply. b isn't declared constexpr either, so it's not clear to me
that the second bullet applies, either, although the wording is rather
odd. If "the object" were changed to "an object," I'd probably
conclude that b can be statically initialized. With the current
wording, "the object" pretty clearly refers to the object being
initialized, so as things stand now, I don't think that b can be
statically initialized. If not, a would be initialized before b, and
in that case, arbitrary user code would be executed before b were
defined.
Scott
>> Regardless of where this definition appears, I'm guaranteed that
>> it occurs before *any* user defined code, including code in
>> constructors of other static objects. In certain cases, that's
>> a useful guarantee.
>
> Is this really true? What about the following at namespace scope?
>
> std::array<int, 3> a = { f(), g(), h() };
> std::array<int, 3> b = { 1, 2, 3 };
>
>> - if an object with static or thread storage duration is initialized such
>> that the initialization satisfies the requirements for the object being
>> declared with constexpr (7.1.5).
>>
> In the code I showed, b isn't a reference, so the first bullet doesn't
> apply. b isn't declared constexpr either, so it's not clear to me
> that the second bullet applies, either, although the wording is rather
> odd.
It shall apply. With a fast scan I couldn't locate where it says that
literals count as constexpr, but 7.1.5 p7 has a matching example that
implies.
I'm convinced that's the intent. You could reword this as "if an
object ... is initialized such that you could add constexpr to the
declarator and still have a valid initializer". The wording is odd and
hard to understand, but not ambiguous.
Sebastian
I thought we had established here
http://groups.google.com/group/comp.std.c++/browse_frm/thread/b14c8fad6735a0d9?hl=en#
that it would work.
BTW - I never got a chance to thank the both of you (Johannes Schaub &
Scott Meyers) then for clarifying this issue for me at the time - so
thanks :)
regards,
Faisal Vali
Radiation Oncology
Loyola
How about
template<class... E>
constexpr
std::array<std::common_type<E...>, sizeof...(E)>
make_array(E... e)
{ return {e...}; }
Yechezkel Mett
> James Kanze wrote:
>
>> On Dec 18, 9:00 pm, Scott Meyers <NeverR...@aristeia.com> wrote:
>>> In TR1, std::tr1::array needed to be an aggregate so that it
>>> could be brace-initialized. To offer the same capability for
>>> C++0x's std::array, the class could simply declare a
>>> constructor taking a std::initializer_list. So why is
>>> std::array an aggregate? Making it a non-aggregate would
>>> permit e.g., giving it a constructor taking a pair of
>>> iterators.
>>
>> Presumably, so that it can be statically initialized, in order
>> to avoid order of initialization problems.
>>
>
> I believe this can all be done using variadic templates:
>
> template<typename T, size_t S>
> struct array {
> template<typename ...U>
> constexpr array(U&&... u):elems{ u... } { }
>
> T elems[S];
> };
>
Actually, to be a constexpr constructor, all the parameter types must be
taken by value so that they are literal types. But since the goal is static-
initialization, i think this won't matter. One could specialize "array" for
literal types to get back some performance for dynamic initialization cases,
i suspect.
I wonder though about differences in behavior to normal aggregate
initialization - are there any? Thanks!
> Johannes Schaub (litb) wrote:
>>
>> I believe this can all be done using variadic templates:
>>
>> template<typename T, size_t S>
>> struct array {
>> template<typename ...U>
>> constexpr array(U&&... u):elems{ u... } { }
>>
>> T elems[S];
>> };
>>
>> array<int, 3> a = { 1, 2 }; // works, is statically initialized
>
> Alas, this won't work, because you can't deduce a template argument's
> type from a brace initializer. The standardizion people have decided
> that such a type can be deduced in the context of auto, but not a
> template. I have no idea why.
>
This is not deducing a template argument from a brace initializer. It will
deduce "U" against "1, 2", not against "{ 1, 2 }" (two arguments 1 and 2 are
given to the constructor), if i'm not mistaken.
> Johannes Schaub (litb) wrote:
>>
>> I believe this can all be done using variadic templates:
>>
>> template<typename T, size_t S>
>> struct array {
>> template<typename ...U>
>> constexpr array(U&&... u):elems{ u... } { }
>>
>> T elems[S];
>> };
>>
>> array<int, 3> a = { 1, 2 }; // works, is statically initialized
>
> Alas, this won't work, because you can't deduce a template argument's
> type from a brace initializer. The standardizion people have decided
> that such a type can be deduced in the context of auto, but not a
> template. I have no idea why.
>
I see what i was missing now. an array of aggregates won't work as we need
to do "{ { .... } }" then :/
Yes, my mistake, I forgot that a templated constructor taking multiple arguments
could be instantiated to match a brace initializer list as long as there is no
constructor taking a std::initializer_list parameter. Thanks for reminding me
of this.
I thus agree that it appears that making std::array a non-aggregate does not
preclude static initialization.
Which brings me back to my question about whether std::array should be defined
to be one.
Scott
Well, if you're willing to use the preprocessor - I believe you can
come even closer to the desired syntax with the following hack:
#define make_array(...) \
std::array<
\
decltype(type_helper(__VA_ARGS__)) \
, sizeof(size_helper(__VA_ARGS__)) \
> __VA_ARGS__; \
/**/
auto a = make_array({1,2,3,4});
And the above relies on the following two functions to work:
1) type_helper is:
template<class T> T type_helper(std::initializer_list<T>);
2) size_helper is slightly more complicated, but is a sequence of
overloads generated
using the BOOST_PP library as follows:
#define PRINT_PARAM(z,n,param) param
#define MAKE_SIZE_HELPERS(z, n, unused) \
struct BOOST_PP_CAT(A,n) { \
template<class T> \
BOOST_PP_CAT(A,n)( \
BOOST_PP_ENUM(n, PRINT_PARAM,T&&) \
);
\
typedef char (&type)[n]; \
}; \
\
BOOST_PP_CAT(A,n)::type \
size_helper(BOOST_PP_CAT(A,n)); \
/**/
BOOST_PP_REPEAT_FROM_TO(1,150,MAKE_SIZE_HELPERS, ~)
#undef PRINT_PARAM
#undef MAKE_SIZE_HELPERS
// The above expands into something along the lines of the following:
struct A1
{
template<class T> A1(T&&);
typedef char (&type)[1];
};
A1::type size_helper(A1);
struct A2
{
template<class T> A2(T&&,T&&);
typedef char (&type)[2];
};
A2::type size_helper(A2);
struct A3 ...
My Preprocessor metaprogramming skills are quite unseasoned, so if
there is a better way to code the above repetition - I would
appreciate any pointers in that direction.
Eitherway, while I do not have a C++0x compiler to check it against,
based on the latest draft (n3000), the above should work.
What do you guys think?
regards,
Faisal Vali
Radiation Oncology
Loyola
> > That still means you have to write the size. An exception
> > isn't as good as a compiler error, and a compiler error
> > isn't as good as not having to write the size to begin with.
> Perhaps, but I don't see your point, because you have to
> manually specify the size of a std::array if you initialize it
> with a brace initializer list, regardless.
That's exactly my point. It still only does half the job; with
a C style array, I don't have to specify the size.
> > What's special is that it can also be initialized
> > statically:
> > std::array< int, 3 > a = { 1, 2, 3 };
> > Regardless of where this definition appears, I'm guaranteed
> > that it occurs before *any* user defined code, including
> > code in constructors of other static objects. In certain
> > cases, that's a useful guarantee.
> Is this really true? What about the following at namespace
> scope?
> std::array<int, 3> a = { f(), g(), h() };
> std::array<int, 3> b = { 1, 2, 3 };
It's really true that std::array can be initialized statically.
Of course, if you don't provide static initializers, it won't
be, but that's the case for a C style array as well.
> Here's what draft C++0x has to say in 3.6.2/2, which I believe
> is where we need to look:
> > Objects with static storage duration (3.7.1) or thread
> > storage duration (3.7.2) shall be zero-initialized (8.5)
> > before any other initialization takes place.
> > Constant initialization is performed:
> > - if each full-expression (including implicit conversions)
> > that appears in the initializer of a reference with static
> > or thread storage duration is a constant expression (5.19)
> > and the reference is bound to an lvalue designating an
> > object with static storage duration or to a temporary (see
> > 12.2)
> > - if an object with static or thread storage duration is
> > initialized such that the initialization satisfies the
> > requirements for the object being declared with constexpr
> > (7.1.5).
>
> > Together, zero-initialization and constant initialization
> > are called static initialization; all other initialization
> > is dynamic initialization.
> In the code I showed, b isn't a reference, so the first bullet
> doesn't apply. b isn't declared constexpr either, so it's not
> clear to me that the second bullet applies, either, although
> the wording is rather odd.
I'm not too clear on all of the latest changes in the standard
either. As specified and implemented in the Boost library, the
intent was clearly that static initialization could be used, and
it certainly fulfills the rules for static initialization
according to the definition in the current standard. But I
really doubt that the intent of the second bullet is to restrict
static initialization to objects actually declared constexpr;
that would break an enormous amount of code.
> If "the object" were changed to "an object," I'd probably
> conclude that b can be statically initialized. With the
> current wording, "the object" pretty clearly refers to the
> object being initialized, so as things stand now, I don't
> think that b can be statically initialized. If not, a would
> be initialized before b, and in that case, arbitrary user code
> would be executed before b were defined.
I'm not sure I follow your reasoning. As I read it, the wording
doesn't say that the object must be declared constexpr; it says
that it must meet the requirements for constexpr, i.e. that
declaring it constexpr would be legal.
--
James Kanze