145

(Note: This question is about not having to specify the number of elements and still allow nested types to be directly initialized.)
This question discusses the uses left for a C array like int arr[20];. On his answer, @James Kanze shows one of the last strongholds of C arrays, it's unique initialization characteristics:

int arr[] = { 1, 3, 3, 7, 0, 4, 2, 0, 3, 1, 4, 1, 5, 9 };

We don't have to specify the number of elements, hooray! Now iterate over it with the C++11 functions std::begin and std::end from <iterator> (or your own variants) and you never need to even think of its size.

Now, are there any (possibly TMP) ways to achieve the same with std::array? Use of macros allowed to make it look nicer. :)

??? std_array = { "here", "be", "elements" };

Edit: Intermediate version, compiled from various answers, looks like this:

#include <array>
#include <utility>

template<class T, class... Tail, class Elem = typename std::decay<T>::type>
std::array<Elem,1+sizeof...(Tail)> make_array(T&& head, Tail&&... values)
{
  return { std::forward<T>(head), std::forward<Tail>(values)... };
}

// in code
auto std_array = make_array(1,2,3,4,5);

And employs all kind of cool C++11 stuff:

  • Variadic Templates
  • sizeof...
  • rvalue references
  • perfect forwarding
  • std::array, of course
  • uniform initialization
  • omitting the return type with uniform initialization
  • type inference (auto)

And an example can be found here.

However, as @Johannes points out in the comment on @Xaade's answer, you can't initialize nested types with such a function. Example:

struct A{ int a; int b; };

// C syntax
A arr[] = { {1,2}, {3,4} };
// using std::array
??? std_array = { {1,2}, {3,4} };

Also, the number of initializers is limited to the number of function and template arguments supported by the implementation.

Community
  • 1
  • 1
Xeo
  • 129,499
  • 52
  • 291
  • 397
  • 11
    @Downvoter: Reason? – Xeo Jun 02 '11 at 20:13
  • That's a great question! First off, I think your make_array could just be replaced by uniform initialization, `std::array x{1,2,3,4,5};`. More importantly, though, imagine you have a class with a member of type std::array. How do you initialize that member in the constructor's initializer list (say to a definite value like {1,2,3})? – Kerrek SB Jun 03 '11 at 17:29
  • @Kerrek: Can't be replaced by what you said, you need to know the number of elements for that. – Xeo Jun 03 '11 at 17:43
  • Xeo: Yes, sorry, I got that wrong -- I meant `std::array x {1,2,3,4,5}`. That line works on its own, but I can't get it to work if `x` is a class member and I want to initialize this in the constructor's initializer list (GCC 4.4). – Kerrek SB Jun 04 '11 at 18:30
  • Check my [edit](http://stackoverflow.com/questions/6114067/how-to-emulate-c-array-initialization-int-arr-e1-e2-e3-behaviour/6272491#6272491) below, I added type safety via an "all types are the same" type trait. – Kerrek SB Jun 08 '11 at 12:54
  • Variadic method. It isn't initialization, more like assignment, but it's the closest I can come to. To get initialization, you'd have to have direct access to the memory. – Lee Louviere May 24 '11 at 17:03
  • Apparently C++0x supports initializer syntax. Awesome. It's like getting to be more like C#, with language support for more complicated support. Anyone know if we get formal language support for interfaces??? – Lee Louviere May 24 '11 at 17:06
  • Where did the C++0x intializer syntax answers go? – Lee Louviere May 24 '11 at 17:16
  • @Xaade: *support for interfaces*?? What do you mean by interfaces? – David Rodríguez - dribeas May 24 '11 at 17:16
  • Unrelated comment. C# has language support for interfaces, it doesn't do much, but it's just that much easier to write. Instead of making a base class and reminding everyone to keep it fully abstract. With C# interface you can't define it. It stays an interface. – Lee Louviere May 24 '11 at 17:36
  • Ah, well, at least I got people thinking in the right direction. – Lee Louviere May 24 '11 at 17:39
  • Sadly a variadic function won't accept `{ { 1, 2 }, { 3, 4 } }`. More work is needed to make that work. – Johannes Schaub - litb May 24 '11 at 18:01
  • @Johannes: Good point actually... – Xeo May 24 '11 at 18:06
  • I don't really consider the lack of `interface` as a problem in the language. Why do you care that the base classes are *fully* abstract or not? They should be what the semantics for that type require, and in many cases that is not *fully* abstract, consider for example, the NVI idiom: virtual methods not being part of the public interface pretty much rules out the possibility of an *all pure virtual functions* class --if you want any functionality at all. – David Rodríguez - dribeas May 25 '11 at 07:59
  • @David Rod: Who said it was a problem. Interface implies fully abstract which is required if you want a true base class in addition to an interface in C#, because the language doesn't support multiple inheritance. You're comparing a base class to the concept of interface. They're not the same thing. If you want an "interface" which isn't the same thing as a class' public interface, then you want a pure virtual abstract base class, Ala COM style. – Lee Louviere May 25 '11 at 18:26
  • @Xeo: Jealousy, in my experience. – Lightness Races in Orbit Jun 24 '13 at 10:16
  • A `decay` is needed, or `int x=3; auto arr=make_array(x);` makes a `std::array` – Yakk - Adam Nevraumont May 13 '14 at 18:45
  • @Yakk: Good point, done. Surprised nobody picked that up until now! – Xeo May 13 '14 at 19:38
  • Is the Intermediate version here, better than the @Konrad Rudolph's simplified version of the C++ standard's proposal, mentioned [here](http://stackoverflow.com/a/26351623/1998099) ? – DarkMatter Oct 20 '14 at 17:23
  • 1
    Apologies, what is the meaning of `TMP` in your question? – kevinarpe Aug 23 '16 at 14:36
  • 1
    @kevinarpe TMP probably stands for _template metaprogramming_. – BeeOnRope May 28 '17 at 20:23

13 Answers13

65

Best I can think of is:

template<class T, class... Tail>
auto make_array(T head, Tail... tail) -> std::array<T, 1 + sizeof...(Tail)>
{
     std::array<T, 1 + sizeof...(Tail)> a = { head, tail ... };
     return a;
}

auto a = make_array(1, 2, 3);

However, this requires the compiler to do NRVO, and then also skip the copy of returned value (which is also legal but not required). In practice, I would expect any C++ compiler to be able to optimize that such that it's as fast as direct initialization.

Pavel Minaev
  • 99,783
  • 25
  • 219
  • 289
  • gcc 4.6.0 doesn't let the second one compile, complaining about narrowing conversion from double to value_type, but clang++ 2.9 is OK with both! – Cubbi May 24 '11 at 17:27
  • 22
    It's with answers like this that I understand most what Bjarne said about feeling "like a new language" :) Variadic templates, late return specifier and type deduction all-in-one! – Matthieu M. May 24 '11 at 17:33
  • @Matthieu: Now add rvalue refs, perfect forwarding and uniform initialization from @DeadMG's code and you've got many new features set. :> – Xeo May 24 '11 at 17:36
  • 1
    @Cubbi: actually, g++ is right here - narrowing conversions are not permitted in aggregate initialization in C++0x (but permitted in C++03 - a breaking change I was not aware of!). I'll remove the second `make_array` call. – Pavel Minaev May 24 '11 at 17:40
  • @Cubbi, yeah, but that is an explicit conversion - it would also permit silent downcasts and other such things.This can still be done by using `static_assert` and some TMP to detect when `Tail` is not implicitly convertible to `T`, and then using `T(tail)...`, but that is left as an exercise for the reader :) – Pavel Minaev May 24 '11 at 17:46
  • @Pavel see http://stackoverflow.com/questions/4434140/narrowing-conversions-in-c0x-is-it-just-me-or-does-this-sound-like-a-breaking :) – Johannes Schaub - litb May 24 '11 at 17:48
  • 1
    In C++17 a slight change to your function gives you guaranteed elision. Remove the variable `a` and return directly. In C++11 you can guarantee elision by doing the size computation in the return value, then doing a `return {{ blah }}`. – Yakk - Adam Nevraumont Aug 25 '16 at 20:24
38

I'd expect a simple make_array.

template<typename ret, typename... T> std::array<ret, sizeof...(T)> make_array(T&&... refs) {
    // return std::array<ret, sizeof...(T)>{ { std::forward<T>(refs)... } };
    return { std::forward<T>(refs)... };
}
einpoklum
  • 118,144
  • 57
  • 340
  • 684
Puppy
  • 144,682
  • 38
  • 256
  • 465
20

Combining a few ideas from previous posts, here's a solution that works even for nested constructions (tested in GCC4.6):

template <typename T, typename ...Args>
std::array<T, sizeof...(Args) + 1> make_array(T && t, Args &&... args)
{
  static_assert(all_same<T, Args...>::value, "make_array() requires all arguments to be of the same type."); // edited in
  return std::array<T, sizeof...(Args) + 1>{ std::forward<T>(t), std::forward<Args>(args)...};
}

Strangely, can cannot make the return value an rvalue reference, that would not work for nested constructions. Anyway, here's a test:

auto q = make_array(make_array(make_array(std::string("Cat1"), std::string("Dog1")), make_array(std::string("Mouse1"), std::string("Rat1"))),
                    make_array(make_array(std::string("Cat2"), std::string("Dog2")), make_array(std::string("Mouse2"), std::string("Rat2"))),
                    make_array(make_array(std::string("Cat3"), std::string("Dog3")), make_array(std::string("Mouse3"), std::string("Rat3"))),
                    make_array(make_array(std::string("Cat4"), std::string("Dog4")), make_array(std::string("Mouse4"), std::string("Rat4")))
                    );

std::cout << q << std::endl;
// produces: [[[Cat1, Dog1], [Mouse1, Rat1]], [[Cat2, Dog2], [Mouse2, Rat2]], [[Cat3, Dog3], [Mouse3, Rat3]], [[Cat4, Dog4], [Mouse4, Rat4]]]

(For the last output I'm using my pretty-printer.)


Actually, let us improve the type safety of this construction. We definitely need all types to be the same. One way is to add a static assertion, which I've edited in above. The other way is to only enable make_array when the types are the same, like so:

template <typename T, typename ...Args>
typename std::enable_if<all_same<T, Args...>::value, std::array<T, sizeof...(Args) + 1>>::type
make_array(T && t, Args &&... args)
{
  return std::array<T, sizeof...(Args) + 1> { std::forward<T>(t), std::forward<Args>(args)...};
}

Either way, you will need the variadic all_same<Args...> type trait. Here it is, generalizing from std::is_same<S, T> (note that decaying is important to allow mixing of T, T&, T const & etc.):

template <typename ...Args> struct all_same { static const bool value = false; };
template <typename S, typename T, typename ...Args> struct all_same<S, T, Args...>
{
  static const bool value = std::is_same<typename std::decay<S>::type, typename std::decay<T>::type>::value && all_same<T, Args...>::value;
};
template <typename S, typename T> struct all_same<S, T>
{
  static const bool value = std::is_same<typename std::decay<S>::type, typename std::decay<T>::type>::value;
};
template <typename T> struct all_same<T> { static const bool value = true; };

Note that make_array() returns by copy-of-temporary, which the compiler (with sufficient optimisation flags!) is allowed to treat as an rvalue or otherwise optimize away, and std::array is an aggregate type, so the compiler is free to pick the best possible construction method.

Finally, note that you cannot avoid copy/move construction when make_array sets up the initializer. So std::array<Foo,2> x{Foo(1), Foo(2)}; has no copy/move, but auto x = make_array(Foo(1), Foo(2)); has two copy/moves as the arguments are forwarded to make_array. I don't think you can improve on that, because you can't pass a variadic initializer list lexically to the helper and deduce type and size -- if the preprocessor had a sizeof... function for variadic arguments, perhaps that could be done, but not within the core language.

Community
  • 1
  • 1
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
17

Using trailing return syntax make_array can be further simplified

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

template <typename... T>
auto make_array(T&&... t)
  -> std::array<std::common_type_t<T...>, sizeof...(t)>
{
  return {std::forward<T>(t)...};
}

int main()
{
  auto arr = make_array(1, 2, 3, 4, 5);
  return 0;
}

Unfortunatelly for aggregate classes it requires explicit type specification

/*
struct Foo
{
  int a, b;
}; */

auto arr = make_array(Foo{1, 2}, Foo{3, 4}, Foo{5, 6});

EDIT No longer relevant: In fact this make_array implementation is listed in sizeof... operator


The code below introduces undefined behavior as per [namespace.std]/4.4

4.4 The behavior of a C++ program is undefined if it declares a deduction guide for any standard library class template.

# c++17 version

Thanks to template argument deduction for class templates proposal we can use deduction guides to get rid of make_array helper

#include <array>

namespace std
{
template <typename... T> array(T... t)
  -> array<std::common_type_t<T...>, sizeof...(t)>;
}

int main()
{
  std::array a{1, 2, 3, 4};
  return 0; 
}

Compiled with -std=c++1z flag under x86-64 gcc 7.0

wiped
  • 715
  • 6
  • 7
  • 10
    C++17 should have a deduction guide already for this: http://en.cppreference.com/w/cpp/container/array/deduction_guides – underscore_d Apr 26 '18 at 14:49
  • Won't such an implementation lead to undefined behavior due to the extension of the standard namespace? https://en.cppreference.com/w/cpp/language/extending_std – Alvov1 Dec 02 '22 at 16:05
  • 1
    @Alvov1 it is, as per [namespace.std]/3.4 in the standard https://stackoverflow.com/questions/58992794/can-i-add-a-deduction-guide-to-std-namespace – wiped Dec 03 '22 at 17:22
9

I know it's been quite some time since this question was asked, but I feel the existing answers still have some shortcomings, so I'd like to propose my slightly modified version. Following are the points that I think some existing answers are missing.


1. No need to rely on RVO

Some answers mention that we need to rely on RVO to return the constructed array. That is not true; we can make use of copy-list-initialization to guarantee there will never be temporaries created. So instead of:

return std::array<Type, …>{values};

we should do:

return {{values}};

2. Make make_array a constexpr function

This allow us to create compile-time constant arrays.

3. No need to check that all arguments are of the same type

First off, if they are not, the compiler will issue a warning or error anyway because list-initialization doesn't allow narrowing. Secondly, even if we really decide to do our own static_assert thing (perhaps to provide better error message), we should still probably compare the arguments' decayed types rather than raw types. For example,

volatile int a = 0;
const int& b = 1;
int&& c = 2;

auto arr = make_array<int>(a, b, c);  // Will this work?

If we are simply static_asserting that a, b, and c have the same type, then this check will fail, but that probably isn't what we'd expect. Instead, we should compare their std::decay_t<T> types (which are all ints)).

4. Deduce the array value type by decaying the forwarded arguments

This is similar to point 3. Using the same code snippet, but don't specify the value type explicitly this time:

volatile int a = 0;
const int& b = 1;
int&& c = 2;

auto arr = make_array(a, b, c);  // Will this work?

We probably want to make an array<int, 3>, but the implementations in the existing answers probably all fail to do that. What we can do is, instead of returning a std::array<T, …>, return a std::array<std::decay_t<T>, …>.

There is one disadvantage about this approach: we can't return an array of cv-qualified value type any more. But most of the time, instead of something like an array<const int, …>, we would use a const array<int, …> anyway. There is a trade-off, but I think a reasonable one. The C++17 std::make_optional also takes this approach:

template< class T > 
constexpr std::optional<std::decay_t<T>> make_optional( T&& value );

Taking the above points into account, a full working implementation of make_array in C++14 looks like this:

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

template<typename T, typename... Ts>
constexpr std::array<std::decay_t<T>, 1 + sizeof... (Ts)>
make_array(T&& t, Ts&&... ts)
    noexcept(noexcept(std::is_nothrow_constructible<
                std::array<std::decay_t<T>, 1 + sizeof... (Ts)>, T&&, Ts&&...
             >::value))

{
    return {{std::forward<T>(t), std::forward<Ts>(ts)...}};
}

template<typename T>
constexpr std::array<std::decay_t<T>, 0> make_array() noexcept
{
    return {};
}

Usage:

constexpr auto arr = make_array(make_array(1, 2),
                                make_array(3, 4));
static_assert(arr[1][1] == 4, "!");
Zizheng Tai
  • 6,170
  • 28
  • 79
5

(Solution by @dyp)

Note: requires C++14 (std::index_sequence). Although one could implement std::index_sequence in C++11.

#include <iostream>

// ---

#include <array>
#include <utility>

template <typename T>
using c_array = T[];

template<typename T, size_t N, size_t... Indices>
constexpr auto make_array(T (&&src)[N], std::index_sequence<Indices...>) {
    return std::array<T, N>{{ std::move(src[Indices])... }};
}

template<typename T, size_t N>
constexpr auto make_array(T (&&src)[N]) {
    return make_array(std::move(src), std::make_index_sequence<N>{});
}

// ---

struct Point { int x, y; };

std::ostream& operator<< (std::ostream& os, const Point& p) {
    return os << "(" << p.x << "," << p.y << ")";
}

int main() {
    auto xs = make_array(c_array<Point>{{1,2}, {3,4}, {5,6}, {7,8}});

    for (auto&& x : xs) {
        std::cout << x << std::endl;
    }

    return 0;
}
Gabriel Garcia
  • 1,000
  • 10
  • 11
  • I overlooked default initialization of the std::array elements. Currently looking for a fix. – Gabriel Garcia Dec 30 '14 at 15:45
  • @dyp I updated the answer with your code. If you decide to write up your own answer, let me know and I will bring mine down. Thank you. – Gabriel Garcia Dec 30 '14 at 17:40
  • 1
    No, it's fine. Binding a temporary array to deduce the length is your idea, and I didn't check if my code even compiles. I think it's still your solution, and answer, with some refinement ;) One might argue though that there's no benefit to a variadic `make_array` as in Puppy's answer, though. – dyp Dec 30 '14 at 19:55
  • Right. Moreover, templates cannot deduce types from initializer lists, which is one of the requirements of the question (nested braced initialization). – Gabriel Garcia Jan 04 '15 at 19:21
5

C++11 will support this manner of initialization for (most?) std containers.

underscore_d
  • 6,309
  • 3
  • 38
  • 64
Richard
  • 3,316
  • 30
  • 41
  • 1
    However, I think OP doesn't want to specify the size of the array, but size is a template parameter of std::array. So you need something like std::array n = {1,2,3,4,5}; – juanchopanza May 24 '11 at 17:48
  • `std::vector<>` doesn't need the explicit integer, and I'm not sure why `std::array` would. – Richard May 26 '11 at 17:31
  • @Richard, because std::vector has dynamic size, and std::array has fixed size. See this: http://en.wikipedia.org/wiki/Array_(C%2B%2B) – juanchopanza May 26 '11 at 17:47
  • @juanchopanza but the `{...}` syntax implies compile-time constant extent, so the ctor should be able to deduce the extent. – Richard May 26 '11 at 19:53
  • I can't find the `initializer_list<>` spec but I suspect something like this might work: `std::initializer_list init_list = {1,2,3}; std::array my_array(init_list);` – Richard May 26 '11 at 20:02
  • 1
    `std::initializer_list::size` is not a `constexpr` function and thus cannot be used like this. There are plans however from libstdc++ (the implementation shipping with GCC) to have their version `constexpr`. – Luc Danton Jun 04 '11 at 08:04
  • @Luc any guess why it isn't `constexpr`? – Richard Jun 06 '11 at 23:52
  • @Richard I don't *know*, but my guess is that the Standard decided to be conservative when they weren't sure how easy it would be for compilers to implement that (because `std::initializer_list` in general needs compiler magic and can't be a library solution). In addition, if it turns out it *is* easy to implement, they can add it later (the reverse situation is more problematic). – Luc Danton Jun 07 '11 at 11:23
2

C++20 UPDATE: Although there are some excellent answers that provide the desired functionality (such as Gabriel Garcia's answer that uses std::index_sequence), I am adding this answer because the simplest way to do this as of C++20 isn't mentioned: just use std::to_array(). Using the OP's last example of an array of structs:

struct A{ int a; int b; };
// C syntax
A arr[] = { {1,2}, {3,4} };
// using std::array
auto std_array = std::to_array<A>({ {1,2}, {3,4} });
drifty0pine
  • 136
  • 1
  • 3
1

С++17 compact implementation.

template <typename... T>
constexpr auto array_of(T&&... t) {
    return std::array{ static_cast<std::common_type_t<T...>>(t)... };
}
Peter
  • 435
  • 1
  • 3
  • 11
1

While this answer is directed more towards this question, that question was marked as a duplicate of this question. Hence, this answer is posted here.

A particular use that I feel hasn't been fully covered is a situation where you want to obtain a std::array of chars initialized with a rather long string literal but don't want to blow up the enclosing function. There are a couple of ways to go about this.

The following works but requires us to explicitly specify the size of the string literal. This is what we're trying to avoid:

auto const arr = std::array<char const, 12>{"some string"};

One might expect the following to produce the desired result:

auto const arr = std::array{"some string"};

No need to explicitly specify the size of the array during initialization due to template deduction. However, this wont work because arr is now of type std::array<const char*, 1>.

A neat way to go about this is to simply write a new deduction guide for std::array. But keep in mind that some other code could depend on the default behavior of the std::array deduction guide.

namespace std {
    template<typename T, auto N>
    array(T (&)[N]) -> array<T, N>;
}

With this deduction guide std::array{"some string"}; will be of type std::array<const char, 12>. It is now possible to initialize arr with a string literal that is defined somewhere else without having to specify its size:

namespace {
    constexpr auto some_string = std::array{"some string"};
}

auto func() {
    auto const arr = some_string;
    // ...
}

Alright, but what if we need a modifiable buffer and we want to initialize it with a string literal without specifying its size?

A hacky solution would be to simply apply the std::remove_cv type trait to our new deduction guide. This is not recommended because this will lead to rather surprising results. String literals are of type const char[], so it's expected that our deduction guide attempts to match that.

It seems that a helper function is necessary in this case. With the use of the constexpr specifier, the following function can be executed at compile time:

#include <array>
#include <type_traits>

template<typename T, auto N>
constexpr auto make_buffer(T (&src)[N]) noexcept {
    auto tmp = std::array<std::remove_cv_t<T>, N>{};

    for (auto idx = decltype(N){}; idx < N; ++idx) {
        tmp[idx] = src[idx];
    }
    return tmp;
}

Making it possible to initialize modifiable std::array-like buffers as such:

namespace {
    constexpr auto some_string = make_buffer("some string");
}

auto func() {
    auto buff = some_string;
    // ...
}

And with C++20, the helper function can even be simplified:

#include <algorithm>
#include <array>
#include <type_traits>

template<typename T, auto N>
constexpr auto make_buffer(T (&src)[N]) noexcept {
    std::array<std::remove_cv_t<T>, N> tmp;
    std::copy(std::begin(src), std::end(src), std::begin(tmp));
    return tmp;
}
303
  • 2,417
  • 1
  • 11
  • 25
0

Create an array maker type.

It overloads operator, to generate an expression template chaining each element to the previous via references.

Add a finish free function that takes the array maker and generates an array directly from the chain of references.

The syntax should look something like this:

auto arr = finish( make_array<T>->* 1,2,3,4,5 );

It does not permit {} based construction, as only operator= does. If you are willing to use = we can get it to work:

auto arr = finish( make_array<T>= {1}={2}={3}={4}={5} );

or

auto arr = finish( make_array<T>[{1}][{2}[]{3}][{4}][{5}] );

None of these look like good solutions.

Using variardics limits you to your compiler-imposed limit on number of varargs and blocks recursive use of {} for substructures.

In the end, there really isn't a good solution.

What I do is I write my code so it consumes both T[] and std::array data agnostically -- it doesn't care which I feed it. Sometimes this means my forwarding code has to carefully turn [] arrays into std::arrays transparently.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
0

If std::array is not a constraint and if you have Boost, then take a look at list_of(). This is not exactly like C type array initialization that you want. But close.

yasouser
  • 5,113
  • 2
  • 27
  • 41
  • that's a good one. and a similar question about using it for assigning of nested structures can be found here [Using-assign-map-list-of-for-complex-types](http://boost.2283326.n4.nabble.com/Using-assign-map-list-of-for-complex-types-td2562465.html) – Assambar Jun 01 '11 at 14:58
0

None of the template approaches worked properly for me for arrays of structs, so I crafted this macro solution:

#define make_array(T, ...) \
    (std::array<T,sizeof((T[]){ __VA_ARGS__ })/sizeof(T)> {{ __VA_ARGS__ }})
auto a = make_array(int, 1, 2, 3);

struct Foo { int x, y; };

auto b = make_array(Foo,
    { 1, 2 },
    { 3, 4 },
    { 5, 6 },
);

Note that although the macro expands its array arguments twice, the first time is inside sizeof, so any side effects in the expression will correctly happen only once.

Have fun!

Boann
  • 48,794
  • 16
  • 117
  • 146