As you probably know 'C++' doesn't allow either passing or returning arrays by value (don't mislead yourself that declaring array parameters will give you that functionality, every parameter declared as array will be 'adjusted' to a pointer to it's first element - which is some kind of odd, but 'C++' holds big legacy). You can use "std::array" for the case but as it have some non-inline methods it will require external linkage and slow-down your program. My wrapper on the other hand is fully inline and will be expanded all in compile-time so heavy optimizations can be performed. As I said it allow passing native arrays and returning them by value. It also allow assigning arrays and some-time in the future I could add addition of them too. The wrapper also allow creation of unnamed and uninitialized temporaries. The wrapper:

    template<typename type>
    struct ugaw //uninitialized temporary generator and array wrapper
    {
        ugaw() {}

        ugaw(const ugaw&) = default;

        ugaw(const type & arg) { *this = *(const ugaw*)arg; } //data-only copy constructor (doesn't invoke any 'type' functions)

        & operator type() & {return d;} //cast operator for a reference to 'type' (used for arrays)

        & operator const type() const & {return d;} //cast operator for a const reference to 'type' (used for arrays)

        ugaw & operator =(const ugaw&) = default;

        ugaw & operator =(const type &arg) { *this = *(const ugaw*)arg; } //data-only assignment operator

        type *operator & () & { return &d; } //address operator to return the actual data stored

        const type *operator & () const & { return &d; } //and a constant version

        type d;
    };

And some uses:

ugaw<int [4]> FuncAdd(ugaw<int [4]> arg_0, const ugaw<int [4]> arg_1)
{
    for(size_t i(0); i < sizeof(arg_0) / sizeof(arg_0[0]); ++i)
        arg_0[i] += arg_1[i];

    return arg_0;
}

int main()
{

    int var[4]{0, 1, 2, 3};

    int var1[4]{1, 1, 2, 3};

    auto &&refArray = FuncAdd(var, var1);

    for(size_t i(0); i < sizeof(var) / sizeof(var[0]); ++i)
        cout << var[i] << endl;

    cout << "Here" << endl;

    for(size_t i(0); i < sizeof(var1) / sizeof(var1[0]); ++i)
        cout << var1[i] << endl;

    cout << "Here" << endl;

    for(size_t i(0); i < sizeof(refArray) / sizeof(refArray[0]); ++i)
        cout << refArray[i] << endl;
        return 0;
}

Life example.

It also have another, more complex usage - declaring unnamed and uninitialized temporaries. This is when you don't care for the output of some function. Example you have:

void FuncDoSomethingAndStoreResult(int (&refArrayOutput)[260]);

This function will do something and store some output in 'refArrayOutput', but sometimes you wouldn't care for it and you'll only want the function to do something for you. For now this was only possible by using default 'zero-initialization', which however is a performance waste. With my wraper you can do that without the performance waste by using default parameter:

void FuncDoSomethingAndStoreResult(int (&refArrayOutput)[260] = const_cast<int (&)[260]>((const int (&)[260])ugaw<int [260]>().d));

So now if you care about the output you will instance the function with a variable name where it will be stored, otherwise you will no pass any arguments.

Examples:

    int var2[260];

    FuncDoSomethingAndStoreResult(var2); //store output in 'var2'

    FuncDoSomethingAndStoreResult(); //store nothing

Recommended Answers

All 3 Replies

I would like to ask why you need to pass your array by value?

Because sometimes you may want to edit data inside the array parameter and if it is passed by a reference or pointer then this could have unwanted effects. Like in my 'FuncAdd' where I've stored the result of the addition in first parameter, which if was a reference to array, would have also edited 'var' in the current example function instance. You can do this manually ofcourse, but the same apply for every other type, why should we ignore arrays?

Cool little snippet of code! It's nice to see some flexing of C++ muscles once in a while.

There are a few issues though.

First, you should also default the move constructor and move assignment operator. You could be wrapping an array of non-trivial types that are actually cheaper to move than to copy, you shouldn't pessimize your code. When you only explicitly default the copy functions, it implicitly deletes the compiler-generated move functions. So, you either have to leave them all to be implicitly defaulted or you need to explicitly default them all. So, either you have this:

    ugaw(const ugaw&) = default;
    ugaw(ugaw&&) = default;
    ugaw & operator =(const ugaw&) = default;
    ugaw & operator =(ugaw&&) = default;

Or you can have none of them. And because your type holds an array, you cannot write your own copy or move functions, they have to be default, explicit or not.

That's another issue with your code, on a practical level. Compilers currently have pretty unreliable behavior when it comes to this stuff (implicit and explicit defaulting rules of C++11). I just solved a critical bug in Boost related to this, which stemmed from the fact that the compilers GCC 4.5, GCC 4.6, GCC 4.9, MSVC (<12), and SGI MIPSpro, all have different, incorrect, and mutually incompatible behavior or support as far as those rules are concerned. The best way to get around it is to either provide user-defined functions for all of them, or leave them all to be implicitly defaulted. But, of course, that's more of a "real-world" issue, not so much an academic issue with the code.

Another issue with your code is that your parametrized constructor initializes the array in its body. If the intend is to have minimal overhead with your wrapper class, then you should avoid that. The reason for this is that because of things like exception handling, the compiler cannot really optimize away the default initialization of the array that is done before you actually initialize it in the body of the constructor. It might be able to do that optimization for trivial types, but I'm not even sure about that. To be safe, you should do the initialization in the initialization list, and to do that, you can use delegating constructors (another C++11 innovation):

    ugaw(const type & arg) : ugaw(*reinterpret_cast<const ugaw*>(&arg[0])) { }

Notice how the constructor simply calls the default copy-constructor with the casted array. You can also do this for a move constructor:

    ugaw(type && arg) : ugaw(move(*reinterpret_cast<ugaw*>(&arg[0]))) { }

Also notice that I changed your C-style cast to a more explicit C++-style cast, because I didn't feel super at ease with your existing cast. There were just too many layers to this cast (array ref --> pointer to first element -- C-style cast --> ugaw pointer). Now, all those layers are explicit and easier to unravel.

And obviously, once you start with any move functions, you have to implement them everywhere, like this:

    ugaw & operator =(type &&arg) { *this = move(*reinterpret_cast<ugaw*>(&arg[0])); } 

    && operator type() && { return move(d); }

In the world of C++11, if you are going to talk about passing things by value, you should always implement move semantics, they go hand-in-hand.

And finally, you might want to consider additional constructors, especially one that takes a std::array (since people shouldn't use C-style static arrays anymore anyways) and one that takes a std::initializer_list. Either way, you will have to deduce the value-type of your array "type", which is good, because you can also use that as a compile-time check to make sure the user actually provides an array type and not something else (or do you want something else to be supported too?).

And at the end of the day, I really don't understand why you cannot use std::array directly instead? The standard array class is guaranteed to be an aggregate type, to have standard layout, and to be trivial (if the value-type allows for it), which is way more than you could ever say about your class. You say that the standard array class is going to be problematic on external linkage boundaries (I assume that you mean that there will be ABI compatibility issues if you use it), but that's not true. The standard array type is standard-layout, meaning that it is one of the few standard classes whose memory layout is guaranteed to be ABI compatible across any platform that comply with the same C ABI (which is fixed for a given target OS and CPU architecture). On that end of things, your class has nothing more to give than what the standard array class already does. Then, the fact that the standard array type is an aggregate type and possibly a trivial type can have substantial performance benefits that you cannot provide with your class the way it is. The only substantial differences here is that you provide implicit conversion to and from a C-style static array, which I am not convinced is a good idea to begin with, I mean, C-style static arrays are one of those things everyone would like to see disappear completely from day-to-day C++ code, in favor of the much superior std::array class (with all its zero-overhead STL-style member functions).

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.