Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

std::array - if only it knew its size

858 views
Skip to first unread message

Ricky65

unread,
Apr 5, 2011, 12:52:32 PM4/5/11
to

I am pleased that C++0x includes a container for fixed-size arrays -
std::array. The obvious advantages being that, unlike built in arrays,
it's easy to get the size of the array using the size() method and the
array doesn't "decay" to a pointer like a built in array and so on. It
goes without saying that these problems with built in arrays cause a
lot of bugs. However, I feel there is a significant shortcoming with
this container - std::array cannot deduce the size of the array from
the initializer list. From what I understand, this is the only
advantage built in arrays have over std::array. This limitation can be
a problem when initializing an array with lots of items.

For example, I have a lot of integers or strings to initialize an
array with, and as there are so many I may not know the total number
of items at the moment of declaration without counting them all. This
can be quite a laborious and time consuming task and is something I'd
rather not have to do. Worst still, as the initializer-list
constructor is being added to the other standard containers, this is
only a problem with std::array. Because of this, I suspect some
programmers in C++0x may choose to initialize a std::vector (or
another container) where std::array would be more appropriate.

For example,
std::vector <int> myintvec = {3, 9, 11, 13 /* ... */}; //ok in c++0x
using initializer_list. works on GCC 4.6
std::array <int> myintarray = {3, 9, 11, 13 /* ... */}; //oh dear,
not possible
Above should have the same functionality of
int myintarray [] = {3, 9, 11, 13 /* ... */};

Sadly, std::array is like the oddball of the containers in this
regard. Also, doesn't this also go against the "uniform
initialization" goal of C++0x? I don't have to specify the number of
items in the initializer list for the other containers such as
std::vector, but I do for std::array. Uniform initialization... except
for std::array.

I can also see this being a problem for novices as they may
intuitively expect std::array to be initialized in the same way as the
other containers and may be surprised to find out that it doesn't work
like they presumed it should.

A lot of programmers would probably rather use this container than a
built in array for fixed-size arrays but with this caveat I suspect
that they may be deterred from adopting it.

This is how I work around this limitation at the moment. In Visual C++
2010 I first declare the array as a built in array e.g. "int
myintarray [] = {3, 9, 11, 13 /* ... */};" . I then use IntelliSense
to get the number of items in the array initializer list e.g. 232, and
finally I replace the built in array with e.g. "std::array <int,
232>myintarray = {3, 9, 11, 13 /* ... */}; ". A far from ideal
workaround. If you know of a less cumbersome way, please share.

It's also an inconvenience if I later want to add more items to the
array initializer list. I must be careful to keep track of the number
of items I am adding so I can update the size parameter accordingly.
If I lose track of the number of items in the initializer list I have
the inconvenience of obtaining the size again.

I'm aware std::array is an aggregate and as such has no constructors
so I assume an initializer-list constructor is no good here. My
understanding is that, at the present time, there is no provision in
the language to deduce the size of the array from the initializer
list. Therefore, I believe this functionality should be added to the
language. Surely it wouldn't be too hard to implement? My opinion is
that somehow this functionality needs to get in there or std::array
may get shunned by many programmers for the aforementioned reason and
that would be a quite a shame and a missed opportunity.

Anyway, that's my two pennies worth on std::array. I will be
interested in reading your replies.


--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

restor

unread,
Apr 5, 2011, 5:24:16 PM4/5/11
to

> I am pleased that C++0x includes a container for fixed-size arrays -
> std::array. The obvious advantages being that, unlike built in arrays,
> it's easy to get the size of the array using the size() method and the
> array doesn't "decay" to a pointer like a built in array and so on. It
> goes without saying that these problems with built in arrays cause a
> lot of bugs.

Hi, I know the focus of your post was on std::array, but I would like
to clarify some things about build-in arrays.
Array-to-pointer decay only takes place when you pass arrays by value.
The
decay does not happen when you pass arrays by reference:

int processArray( int (&array)[10] ); // no decay

Also, it is fairly easy to implement the function that would compute
the
size of the array:

template< typename T, size_t I >
size_t size( T(&arr)[I] )
{
return I;
}

Regards,
&rzej

Marc

unread,
Apr 5, 2011, 5:22:31 PM4/5/11
to

Ricky65 wrote:

> For example, I have a lot of integers or strings to initialize an
> array with, and as there are so many I may not know the total number
> of items at the moment of declaration without counting them all. This
> can be quite a laborious and time consuming task and is something I'd
> rather not have to do. Worst still, as the initializer-list
> constructor is being added to the other standard containers, this is
> only a problem with std::array. Because of this, I suspect some
> programmers in C++0x may choose to initialize a std::vector (or
> another container) where std::array would be more appropriate.
>
> For example,
> std::vector <int> myintvec = {3, 9, 11, 13 /* ... */}; //ok in c++0x
> using initializer_list. works on GCC 4.6
> std::array <int> myintarray = {3, 9, 11, 13 /* ... */}; //oh dear,
> not possible
> Above should have the same functionality of
> int myintarray [] = {3, 9, 11, 13 /* ... */};

Note that the type isn't std::array<int> but std::array<int,232>, so
there is no way something like this can work.

However, you could write:
auto myintarray=make_array<int>(3, 9 /*...*/);

where make_array uses variadic templates and sizeof... to get the
number of elements.
(depending on how exactly you implement make_array, <int> may be
unneeded)

Sadly, you can't write make_array({3, 9 /*...*/}) because there is no
constexpr way to get the size of an initializer_list.

Daniel Krügler

unread,
Apr 5, 2011, 5:22:42 PM4/5/11
to

Am 05.04.2011 18:52, schrieb Ricky65:
>
> I am pleased that C++0x includes a container for fixed-size arrays -
> std::array. The obvious advantages being that, unlike built in arrays,
> it's easy to get the size of the array using the size() method and the
> array doesn't "decay" to a pointer like a built in array and so on. It
> goes without saying that these problems with built in arrays cause a
> lot of bugs. However, I feel there is a significant shortcoming with
> this container - std::array cannot deduce the size of the array from
> the initializer list. From what I understand, this is the only
> advantage built in arrays have over std::array. This limitation can be
> a problem when initializing an array with lots of items.

Yes, I agree

I think what you want is something like make_array as suggested in

http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-closed.html#851

This issues has been deferred as NAD future, because there was no pressing need to provide this for C++0x.

Until that point you can simply define your own make_array as described in the issue:

#include <type_traits>
#include <utility>
#include <array>

template <typename... T>
std::array<typename std::decay<typename std::common_type<T...>::type
>::type, sizeof...(T)>
make_array(T&&... t)
{
typedef typename std::decay<typename std::common_type<T...>::type
>::type U;
std::array<U, sizeof...(T)> result = {
static_cast<U>(std::forward<T>(t))...
};
return result;
}

int main()
{
auto a = make_array(1, 2); // OK: std::array<int, 2>
auto b = make_array(1, 1.2, 2u); // OK: std::array<double, 3>
}

HTH & Greetings from Bremen,

Daniel Krügler

Daniel Krügler

unread,
Apr 6, 2011, 3:38:56 PM4/6/11
to

Am 05.04.2011 23:22, schrieb Daniel Krügler:
>
> Am 05.04.2011 18:52, schrieb Ricky65:
>>
>> I am pleased that C++0x includes a container for fixed-size arrays -
>> std::array. The obvious advantages being that, unlike built in arrays,
>> it's easy to get the size of the array using the size() method and the
>> array doesn't "decay" to a pointer like a built in array and so on. It
>> goes without saying that these problems with built in arrays cause a
>> lot of bugs. However, I feel there is a significant shortcoming with
>> this container - std::array cannot deduce the size of the array from
>> the initializer list. From what I understand, this is the only
>> advantage built in arrays have over std::array. This limitation can be
>> a problem when initializing an array with lots of items.

[..]

> I think what you want is something like make_array as suggested in
>
> http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-closed.html#851
>
> This issues has been deferred as NAD future, because there was no
> pressing need to provide this for C++0x.
>
> Until that point you can simply define your own make_array as described
> in the issue:
>
> #include <type_traits>
> #include <utility>
> #include <array>
>
> template <typename... T>
> std::array<typename std::decay<typename std::common_type<T...>::type
>> ::type, sizeof...(T)>
> make_array(T&&... t)
> {
> typedef typename std::decay<typename std::common_type<T...>::type
> >::type U;
> std::array<U, sizeof...(T)> result = {
> static_cast<U>(std::forward<T>(t))...
> };
> return result;
> }
>
> int main()
> {
> auto a = make_array(1, 2); // OK: std::array<int, 2>
> auto b = make_array(1, 1.2, 2u); // OK: std::array<double, 3>
> }

One reason for not standardizing too early things is that you can
improve your original ideas. Also, since then some core rules have
changed in advantage of the programmer. Since the original proposal done
in LWG 851 I had developed make_array a bit further and here is my
currently best approximation: The advantages are:

1) make_array can now be used in constant expressions
2) Users can provide there own destination type
3) Support for empty arrays
4) Validation that all arguments are implicitly convertible to the
destination type (Remove this rule, if you don't like it, or replace it
by a corresponding is_constructible test or sfinae the template away).

#include <type_traits>
#include <utility>
#include <array>

// Assume that std::forward were required to be constexpr...
template <class T>
constexpr T&&
cforward(typename std::remove_reference<T>::type& t) {
return static_cast<T&&>(t);
}

template <class T>
constexpr T&&
cforward(typename std::remove_reference<T>::type&& t) {
static_assert(!std::is_lvalue_reference<T>::value,
"T must not be an lvalue-reference");
return static_cast<T&&>(t);
}

template<class T, class... U>
struct all_convertible_to;

template<class T>
struct all_convertible_to<T> : std::true_type {};

template<class T, class U>
struct all_convertible_to<T, U> : std::is_convertible<U, T> {};

template<class T, class U, class... R>
struct all_convertible_to<T, U, R...> : std::conditional<
std::is_convertible<U, T>::value, all_convertible_to<T, R...>,
std::false_type>::type::type
{};

struct default_array_type_t;

template<class T, class...>
struct deduce_array_type
{
typedef T type;
};

template<class... T>
struct deduce_array_type<default_array_type_t, T...>


{
typedef typename std::decay<
typename std::common_type<T...>::type

>::type type;
};

// Sfinae protection for the empty sequence case: Use code is
// required to provide the destination type
template<>
struct deduce_array_type<default_array_type_t>
{
};

template <class D = default_array_type_t, class... T>
constexpr auto make_array(T&&... t) ->
std::array<typename deduce_array_type<D, T...>::type, sizeof...(T)>
{
typedef typename deduce_array_type<D, T...>::type U;
static_assert(all_convertible_to<U, T...>::value,
"Argument types must be implicitly convertible to destination type");
return std::array<U, sizeof...(T)>{
static_cast<U>(cforward<T>(t))...
};
}

int main()
{
auto a = make_array(1, 2);

auto b = make_array(5, 1.2);
auto c = make_array<float>(-12, 7.3);
auto d = make_array<bool>();
constexpr auto ca = make_array(-1, +1, -2, +2);

Miles Bader

unread,
Apr 6, 2011, 3:40:46 PM4/6/11
to

restor <akrz...@gmail.com> writes:
> Also, it is fairly easy to implement the function that would compute
> the size of the array:
>
> template< typename T, size_t I >
> size_t size( T(&arr)[I] )
> {
> return I;
> }

That is a cool function, and generally seems to work, but I noticed
that it yields an error for a zero-sized array (e.g. "int foo[0]", or
"int foo[] = {}"). Are zero-sized arrays invalid in C++, or ...?

E.g.:

template <typename T, unsigned I>
unsigned array_size(T (&arr)[I])
{
return I;
}

static int array[] = {};

int test ()
{
return array_size (array);
}

Results in the following errors from gcc (any recent version):

x.cc: In function 'int test()':
x.cc:11:27: error: no matching function for call to 'array_size(int [0])'
x.cc:11:27: note: candidate is:
x.cc:2:32: note: template<class T, unsigned int I> unsigned int
array_size(T (&)[I])

[The compiler doesn't complain about zero-sized array declarations
themselves though...]

Thanks,

-Miles

--
Is it true that nothing can be known? If so how do we know this? -Woody
Allen

Daniel Krügler

unread,
Apr 6, 2011, 6:19:36 PM4/6/11
to
Am 06.04.2011 21:40, schrieb Miles Bader:
>
> restor<akrz...@gmail.com> writes:
>> Also, it is fairly easy to implement the function that would compute
>> the size of the array:
>>
>> template< typename T, size_t I>
>> size_t size( T(&arr)[I] )
>> {
>> return I;
>> }
>
> That is a cool function, and generally seems to work, but I noticed
> that it yields an error for a zero-sized array (e.g. "int foo[0]", or
> "int foo[] = {}"). Are zero-sized arrays invalid in C++, or ...?

Yes, these are invalid, see below.

> E.g.:
>
> template<typename T, unsigned I>
> unsigned array_size(T (&arr)[I])
> {
> return I;
> }
>
> static int array[] = {};
>
> int test ()
> {
> return array_size (array);
> }
>
> Results in the following errors from gcc (any recent version):
>
> x.cc: In function 'int test()':
> x.cc:11:27: error: no matching function for call to 'array_size(int [0])'
> x.cc:11:27: note: candidate is:
> x.cc:2:32: note: template<class T, unsigned int I> unsigned int
> array_size(T (&)[I])
>
> [The compiler doesn't complain about zero-sized array declarations
> themselves though...]


The above definition of array is invalid already in C++03, see
[dcl.init.aggr] p. 4:

"An array of unknown size initialized with a brace-enclosed
initializer-list containing n initializers, where n shall be greater
than zero, is defined as having n elements (8.3.4). [...] An empty
initializer list {} shall not be used as the initializer for an array of
unknown bound." with footnote 91:

"The syntax provides for empty initializer-lists, but nonetheless C++
does not have zero length arrays."

HTH & Greetings from Bremen,

Daniel Krügler


--

Ricky65

unread,
Apr 7, 2011, 4:07:16 AM4/7/11
to

Thank you for you're feedback. The make_array template you provided
seems like a good solution for now, thanks for sharing it. However, am
I right in saying that variadic templates only take a finite number of
arguments?

After reading the standards committee link I am hopefull that this
will be addressed at the next revision of the standard. Personally I'd
eventually like to be able to do "std::array<int> a = { 1, 4, 6 };"
but it's not worth fussing over I guess.

Thanks

Ricky Marcangelo

Daniel Krügler

unread,
Apr 7, 2011, 7:34:01 PM4/7/11
to

On 2011-04-07 10:07, Ricky65 wrote:
> Thank you for you're feedback. The make_array template you provided
> seems like a good solution for now, thanks for sharing it. However, am
> I right in saying that variadic templates only take a finite number of
> arguments?

I'm not sure that I properly understand your question: How would you call a variadic function with an infinite number of arguments?

The requirements on variadic template parameter are indirectly described by those on template arguments and function call arguments. The guidelines for the minimum number of to be supported template arguments is 1024, which compares quite well against the corresponding guideline in regard to the minimum number of arguments in any function call (256).

Do you have an example where this pure-library solution would not work?

[Let me add that my make_array suggestion requires a fix as noted by Johannes Schaub in another message: It currently relies on the fact that the used form of list-initialization with elided braces is supported, which is incorrect. Also needed is a special solution for the zero-length case, because there exists no portable unique syntax based on list-initialization for std::array. Fortunately we can fall-back to the well-known parenthesized form of value-initialization here, which I will fix in my follow-up contribution]

> After reading the standards committee link I am hopefull that this
> will be addressed at the next revision of the standard. Personally I'd
> eventually like to be able to do "std::array<int> a = { 1, 4, 6 };"
> but it's not worth fussing over I guess.

I think this will be very hard to realize without considerable change of the language, thus this won't happen before the nest standard revision and would probably require a good proposal that explains how that should work in the general case. I doubt the core language would accept an extra rule limited to the library component std::array ;-)

HTH & Greetings from Bremen,

Daniel Krügler


Daniel Krügler

unread,
Apr 7, 2011, 7:36:48 PM4/7/11
to

On 2011-04-06 21:38, Daniel Krügler wrote:
>
> Since the original proposal done
> in LWG 851 I had developed make_array a bit further and here is my
> currently best approximation: The advantages are:
>
> 1) make_array can now be used in constant expressions
> 2) Users can provide there own destination type
> 3) Support for empty arrays
> 4) Validation that all arguments are implicitly convertible to the
> destination type (Remove this rule, if you don't like it, or replace it
> by a corresponding is_constructible test or sfinae the template away).

[..]

As pointed out by Johannes Schaub in another thread, I used a non-portable initialization syntax for the std::array in my previous message. And as pointed out by myself in a reply to his message there is the second problem that there does not exist a portable direct-list-initialization syntax for zero-length arrays (which should be fixed by a library issue). The following implementation tries to solve this issues via a helper factory, fortunately not loosing the above features:

#include <type_traits>
#include <utility>
#include <array>

// Just imagine that std::forward were required to be constexpr...


template <class T>
constexpr T&&
cforward(typename std::remove_reference<T>::type& t)
{
return static_cast<T&&>(t);
}

template <class T>
constexpr T&&
cforward(typename std::remove_reference<T>::type&& t)
{
static_assert(!std::is_lvalue_reference<T>::value,
"T must not be an lvalue-reference"
);
return static_cast<T&&>(t);
}

template<class T, class... U>
struct all_convertible_to;

template<class T>
struct all_convertible_to<T> : std::true_type {};

template<class T, class U>

struct all_convertible_to<T, U> : std::is_convertible<U, T>::type {};

template<class T, class U, class... R>
struct all_convertible_to<T, U, R...> : std::conditional<
std::is_convertible<U, T>::value, all_convertible_to<T, R...>,
std::false_type>::type::type
{
};

// This tag type is used to detect whether we perform automatic
// type deduction, else the type is user-provided:
struct auto_element_type_t;

template<class T, class...>
struct deduce_element_type
{
typedef T type;
};

template<class... T>
struct deduce_element_type<auto_element_type_t, T...>


{
typedef typename std::decay<typename std::common_type<T...>::type>::type type;
};

// Sfinae protection for the empty sequence case: User code
// is required to provide the destination type in this case:
template<>
struct deduce_element_type<auto_element_type_t>
{
};

template<class T, class... U>

struct array_creator
{
static constexpr std::array<T, sizeof...(U)> create(U&&... u)
{
return std::array<T, sizeof...(U)>{ {static_cast<T>(cforward<U>(u))...} };
}
};

template<class T>
struct array_creator<T>
{
static constexpr std::array<T, 0> create()
{
return std::array<T, 0>();
}
};

template <class D = auto_element_type_t, class... T>


constexpr auto make_array(T&&... t) ->

std::array<typename deduce_element_type<D, T...>::type, sizeof...(T)>
{
typedef typename deduce_element_type<D, T...>::type U;
static_assert(all_convertible_to<U, T...>::value,
"All argument types must be implicitly convertible to the element type of std::array"
);
return array_creator<U, T...>::create(cforward<T>(t)...);
}

int main()
{
auto a = make_array(1, 2);
auto b = make_array(5, 1.2);
auto c = make_array<float>(-12, 7.3);
auto d = make_array<bool>();
constexpr auto ca = make_array(-1, +1, -2, +2);

constexpr auto cb = make_array<bool>();
constexpr auto i1 = 2, i2 = 3;
constexpr auto cc = make_array(i1, i2);
}

Enjoy & Greetings from Bremen,

Daniel Krügler

--

Ricky65

unread,
Apr 10, 2011, 5:46:57 AM4/10/11
to
On Apr 8, 12:34 am, Daniel Krügler <daniel.krueg...@googlemail.com>
wrote:

> I'm not sure that I properly understand your question: How would you call a variadic function with an infinite number of arguments?
>
> The requirements on variadic template parameter are indirectly described by those on template arguments and function call arguments. The guidelines for the minimum number of to be supported template arguments is 1024, which compares quite well against the corresponding guideline in regard to the minimum number of arguments in any function call (256).
>
> Do you have an example where this pure-library solution would not work?

Sorry, my original question was very poorly phrased. I meant to ask
what the limit of variadiac template arguments was.

The miniumum number of arguments, 1024, seems ample for my needs.
However, does this mean we could run into problems with using
make_array for initializing 2000 values, for example? If so, I would
question the robustness of this solution.

Anyway, I thank you again for providing this template class as it is
of benefit to me. On a side note, most C++ programmers, like I was,
are unaware of make_array. As it would be of convenience to a good
number of programmers maybe it would benefit from being "advertised"?

0 new messages