How do I initialize an array with a function?

Oct 4, 2016 at 12:36pm
I wanted to do something like:

1
2
3
int myArr[4];

setArray(myArr,4, 1, 7, 9);


Note that 4 elements are passed to the function after the array, because 4 is the size of myArr.

[*] If the size is N and I pass more than N elements, then only the first N ones will be taken into account.

1
2
// Here, 11,22 and 33 are ignored
setArray(myArr,4, 1, 7, 9, 11, 22, 33);


[*] If the size is N and I pass less than N elements, a compilation error is thrown

1
2
// Error: myArr size is 4 and only 1 element is passed
setArray(myArr, 9);


If throwing a compile-time error is a problem, then another solution would be to not initialize/modify the remaining elements.

I did this system using std::initializer_list<> but the problem is that I need to use curly braces

 
setArray(myArr, {0,1,7,2} );


And I don't like that.

I was trying to achieve this with VARIADIC TEMPLATES but I'm having bad times trying to figure out how.

This is the function signature

1
2
3
4
5
template<typename T, typename... Values, size_t size>
void setVector(T (&vec)[size], Values... pack)
{

}


T is the type of the vector
Values... is a parameter pack
size is the fixed size of the array

I want to take into account only the first SIZE elements of the parameter pack.
The other ones have to be ignored.
And if the parameter pack elements aren't enough, then the remaining elements won't be initialized/modified.

How can I accomplish this?

[PLEASE NOTE]

This is an exercise. I know there are other ways to do this and I know it's not the best solution to this.
So please don't give me that.

Thanks!
Last edited on Oct 4, 2016 at 12:41pm
Oct 4, 2016 at 2:17pm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
#include <string>

namespace detail_
{
    template < typename T, std::size_t N, std::size_t POS > struct assign_helper
    {
        template< typename FIRST, typename... REST >
        static void assign( T (&vec)[N], FIRST&& first, REST&&... rest )
        {
             vec[POS] = std::forward<FIRST>(first) ;
             assign_helper<T,N,POS+1>::assign( vec, std::forward<REST>(rest)... ) ;
        }
    };

    template < typename T, std::size_t N > struct assign_helper< T, N, N >
    { template < typename... REST > static void assign( T (&)[N], const REST&... ) {} };
}

template < typename T, std::size_t N, typename... U >
void assign( T (&vec)[N], U&&... pack )
{
    static_assert( sizeof...(pack) >= N, "too few values" ) ;
    detail_::assign_helper< T, N, 0 >::assign( vec, std::forward<U>(pack)... ) ;
}

int main()
{
    int a[6]{} ;
    assign( a, 1, 2, 3, 4, 5, 6, 7, 8 ) ;
    for( int v : a ) std::cout << v << ' ' ;
    std::cout << '\n' ;
}

http://coliru.stacked-crooked.com/a/834b9dff6d0cbf4b
http://rextester.com/YPD54237
Oct 4, 2016 at 2:55pm
That was definitely ingenious.

I have some questions now:

1) Why do you use rvalue-references (and then std::forward<>)? My thought is that, since rvalue reference + generic type = universal reference ... Maybe to keep values the way they really are for the assignment?

2) In the assign_helper struct specialization (<T, N, N>) there's another function assign() which does nothing. Clearly, this is the case where we ignore the elements in excess. But this time, why are you passing a const reference to REST...? You always used to pass a rvalue-reference to REST in the "normal" assign() function.

Thanks boy!
Last edited on Oct 4, 2016 at 2:57pm
Oct 4, 2016 at 3:38pm
> 1) Why do you use rvalue-references (and then std::forward<>)? My thought is that,
> since rvalue reference + generic type = universal reference ...
> Maybe to keep values the way they really are for the assignment?

Yes, to exploit move semantics, where possible.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int main()
{
    struct A
    {
        A( std::string s = {} ) : str( std::move(s) ) {}

        A& operator= ( const A& that ) { str = that.str ; std::cout << str << ": copy assign A\n" ; return *this ; };
        A& operator= ( A&& that ) noexcept { str = std::move(that.str) ; std::cout << str << ": move assign A\n" ; return *this ; };
        A& operator= ( const char* cstr ) { str = cstr ; std::cout << str << ": assign NTBS\n" ; return *this ; };


        // rest of rule of five (elided for brevity)

        std::string str ;
    };

    A one("one"), three("three"), five("five") ;

    A arr[6] ;

    assign( arr, one, "two", three, A("four"), std::move(five), A("six") ) ;
    // copy assign one, assign NTBS, copy assign three, move assign rvalue, move assign five, move Assign rvalue
}

http://coliru.stacked-crooked.com/a/410c5e3cdfa53d37

Note: N4164 - 'forwarding reference', not 'universal reference' https://isocpp.org/files/papers/N4164.pdf


> 2) In the assign_helper struct specialization (<T, N, N>) there's another function assign()
> which does nothing. Clearly, this is the case where we ignore the elements in excess.
> But why are you passing a const reference to REST... and not a rvalue-reference to REST... ?

Here, there is no advantage in preserving rvalueness with perfect forwarding.
(It is blithely ignored, whether it is an lvalue or it is an rvalue).
Oct 4, 2016 at 4:56pm
Thanks.

I have made some tests changing U&& to just U but there's something strange here. Here's the simulation: http://coliru.stacked-crooked.com/a/8c2303423529dba8

I have called assign() first one-by-one, and then once for all the elements.

In the one-by-one test, a Copy Constructor is called for the objects one, three and five .

Nothing strange here.

In the all-in-once function call, we know that the assign() function should IGNORE all the elements in the pack except the first one (because, if you see, I set the array to be large 1 elements).

So, only the object one should be taken into account, and I should be able to only see

1
2
one: copy ctor A
one: move assign A


Instead, what I get is:


1
2
3
4
five: copy ctor A       // where is this coming from???
three: copy ctor A    // where is this coming from???
one: copy ctor A
one: move assign A


There must be something I don't get

Last edited on Oct 4, 2016 at 4:58pm
Oct 4, 2016 at 5:03pm
What I thought is:
maybe the objects five and three are being copy-constructed when the specialized assign() function is called on them.

Okay, maybe that's the reason... but why are they being copy-constructed BEFORE the object one is being (copied-constructed and) assigned?

I mean, the object one should be the first one to be processed
Oct 5, 2016 at 2:21am
> I have made some tests changing U&& to just U

The parameters are passed by value => copies of objects are passed to the function.

assign(arr2, one, "two", three, A("four"), std::move(five), A("six"));
Pass by value:
a. copy construct one, three, five
b. construct two with A::A( std::string )
c. construct in situ four and six with A::A( std::string ) (copy elision, up to the implementation)

Note that std::move(five) is copy constructed because the move constructor for A is implicitly deleted.
(Since A has a user-declared copy constructor.)
Oct 5, 2016 at 2:39am
> I mean, the object one should be the first one to be processed

They may be processed in any order, and pre-C++17, their evaluations may interleave.

Function call: value computations and side effects of the initialisation of a parameter is unsequenced
(C++17: indeterminately sequenced) with respect to value computations and side effects of the initialisation of any other parameter.

1
2
3
4
5
6
7
int foo( int a, int b ) { return a + b ; }

int main()
{
    int i = 0 ;
    foo( i, ++i ) ; // undefined behaviour in C++14
}
Last edited on Oct 5, 2016 at 2:40am
Oct 5, 2016 at 5:46am
Ok, function parameters are initialized randomly.

In this case we are dealing with a PARAMETER PACK.

The elements of the parameter pack must be in order as I type them when I call the function. Otherwise, it would have no sense.

In MY case, the objects FIVE and THREE are processed first, so it means that the paramter pack is first filled with those objects?
Well, the ansewer is no. In fact, when I print the array I can see that the object ONE is printed. But then that means that the object ONE was the first in the parameter list(???)

In case you didn't understand, short question: FIVE and THREE objects are processed first, but they don't seem to be the first objects in the pack. The first object in the pack is ONE. Why wasn't ONE processed first then?
What's going on?
Last edited on Oct 5, 2016 at 5:49am
Oct 5, 2016 at 8:02am
@JLBorges

Can I ask why you write code that way?
Like putting braces on the same line and struct keyword on the same line as the template, etc.

For me it's unreadable. I'm getting a headache!
Oct 5, 2016 at 9:03am
You just need to be more confident. It's enough he wrote that sample for me
Oct 5, 2016 at 9:47am
@gedamial

Getting defensive...
Oct 5, 2016 at 12:47pm
> Why wasn't ONE processed first then? What's going on?

Consider: void foo( int a, int b, int c, int d ) ;
And the call: foo( e1, e2, e3, e4 ) ;

e1, e2, e3 and e4 may be evaluated in any order; what is required is that the activation record for foo must created in such a way that:
the result of evaluating e1 is the first argument, the result of evaluating e2 is the second argument and so on.


Consider: template < typename... ARGS > void bar( ARGS... pack ) ;
And the call: bar( e1, e2, e3, e4 ) ;

e1, e2, e3 and e4 may be evaluated in any order; what is required is that the argument pack must created in such a way that the pack expansion pack... produces:
result of evaluating e1, result of evaluating e2, result of evaluating e3, result of evaluating e4


The order of evaluation may be different with different implementations (or, at least theoretically, with the same implementation at different places in the code or at the same place in code during two different runs of the same program).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>

void foo( int a, int b, int c, int d ) { std::cout << "foo(" << a << ',' << b << ',' << c << ',' << d << ")\n" ; }

template < typename... ARGS > void bar( ARGS... pack ) { std::cout << "bar => expand pack and call " ; foo(pack...) ; }

int eval( int a ) { return ( std::cout << "eval(" << a << ") - " ), a ; }

int main()
{
    foo( eval(1), eval(2), eval(3), eval(4) ) ;

    bar( eval(1), eval(2), eval(3), eval(4) ) ;
}

------ clang++ --------
eval(1) - eval(2) - eval(3) - eval(4) - foo(1,2,3,4)
eval(1) - eval(2) - eval(3) - eval(4) - bar => expand pack and call foo(1,2,3,4)

--------- g++ ---------
eval(4) - eval(3) - eval(2) - eval(1) - foo(1,2,3,4)
eval(4) - eval(3) - eval(2) - eval(1) - bar => expand pack and call foo(1,2,3,4)

http://coliru.stacked-crooked.com/a/ed7ba99086a9769f
Oct 5, 2016 at 5:54pm
Thanks dude.

Now I would like to make a variant of this.

I'd like to default-initialize the missing values

1
2
3
int ints[3];

assign(ints, 11, 22);   // two values passed, the third is default (0) 


To achieve this, I tried to imagine the case where only the vector is passed, because the parameter pack is empty.
Unfortunately, an empy parameter pack cannot be expanded so I came up with a stupid solution where, in case the pack is empty, a boolean is passed instead. And an overloaded function which accepts a boolean as second parameter is called.

But i'm making a mess: http://coliru.stacked-crooked.com/a/4bb0c4a7965d507e

I need help on achieving this. I tried something really stupid but of course it doesn't work.

Any help?

Last edited on Oct 5, 2016 at 5:58pm
Oct 5, 2016 at 6:47pm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
namespace detail_
{
    /////////////////////////////////////// assign_helper ////////////////////////////////
    template < typename T, std::size_t N, std::size_t POS = 0 > struct assign_helper
    {
        template< typename FIRST, typename... REST >
        static void assign( T (&vec)[N], FIRST&& first, REST&&... rest )
        {
             vec[POS] = std::forward<FIRST>(first) ;
             assign_helper<T,N,POS+1>::assign( vec, std::forward<REST>(rest)... ) ;
        }
    };

    template < typename T, std::size_t N > struct assign_helper< T, N, N >
    { template < typename... REST > static void assign( T (&)[N], const REST&... ) {} };
    ////////////////////////////////////////////////////////////////////////////////////////


    ///////////////////////////////// missing argument privider ////////////////////////////
    template < typename T, std::size_t N, bool NO_MISSING_ARGS, typename... U >
    struct missing_argument_provider // NO_MISSING_ARGS == true: no missing argument
    {
        static void assign( T (&vec)[N], U&&... pack )
        { detail_::assign_helper< T, N >::assign( vec, std::forward<U>(pack)... ) ; }
    };

    template < typename T, std::size_t N, typename... U >
    struct missing_argument_provider< T, N, false, U... > // NO_MISSING_ARGS == false: at least one missing argument
    {
        static void assign( T (&vec)[N], U&&... pack ) // add one more argument, and try again
        { missing_argument_provider< T, N, ( sizeof...(U) >= N ), U..., T >::assign( vec, std::forward<U>(pack)..., T{} ) ; }
    };
    ////////////////////////////////////////////////////////////////////////////////////////
}

template < typename T, std::size_t N, typename... U > void assign( T (&vec)[N], U&&... pack )
{ detail_::missing_argument_provider< T, N, ( sizeof...(U) >= N ), U... >::assign( vec, std::forward<U>(pack)... ) ; }

http://coliru.stacked-crooked.com/a/543e136c355a66fd
Oct 5, 2016 at 7:43pm
This is so ingenious...

Three questions:

1) Where did you get all this knowledge about template meta-programming AND ESPECIALLY variadic templates? Have some good books to recommend?

2) I don't get a thing with your code. On line 31 you wrote

 
missing_argument_provider< T, N, (sizeof...(U) >= N), U..., T >::assign(...)


Look at the Template Parameters:

T > is the type of the vector
N > size of the vector
sizeof...() > boolean value to determine which template to look for
U... > the parameter pack
T > WHAT? Again? Why?

The template "signature" requires only 4 parameters

 
template < typename T, size_t N, bool, typename... U>


My thought is that since you're passing an additional value (which is a default-initialized T{} ) then you're kind of "explicitly saying" to the template that it has to expect an additional T

I'm curious

3) What if I want to do something like

1
2
3
int ints[3] { 1, 2, 3 };   // array already initialized

assign(ints, 3);    // changes only the first value - final array = {3, 2, 3} 



Last edited on Oct 5, 2016 at 9:32pm
Oct 6, 2016 at 3:44am
> Have some good books to recommend?

Most of the good books that I'm aware of are dated (written before C++11). But the TMP techniques that they expound are still very useful.

'Modern C++ Design' by Alexandrescu https://www.amazon.com/dp/0201704315
'Advanced C++ Metaprogramming' by Gennaro https://www.amazon.com/dp/1460966163
'C++ Template Metaprogramming' by Abrahams and Gutovoy https://www.amazon.com/dp/0321227255/

Contrary to what most 'career teachers' believe, one of the most effective ways of learning programming is by studying code written by people more knowledgeable than oneself. The boost libraries have a vast amount of high quality TMP code; one can learn a lot from perusing the Boost code base.


> On line 31 you wrote missing_argument_provider< T, N, (sizeof...(U) >= N), U..., T >::assign(...)

I made a mistake there; should have been
missing_argument_provider< T, N, ( sizeof...(U) >= (N-1) ), U..., T >::assign(...)
Corrected code: http://coliru.stacked-crooked.com/a/83636732bb3ef243

> My thought is that since you're passing an additional value (which is a default-initialized T{} )
> then you're kind of "explicitly saying" to the template that it has to expect an additional T

Let us say, the pack expansion some_pack... of size three produces a, b, c
Then some_pack..., d produces a, b, c, d
When used in a variadic template, a, b, c, d would become a single pack of size four.


> 3) What if I want to do something like

Make an attempt. You should be able to do it by now.
Oct 6, 2016 at 1:43pm
Oh yes, of course I've tried by myself.

The problem is that template metaprogramming is really tough. You need to think in terms of template inheritance, template parameters and constant values all the time.

When I read templated code I understand it, I comprehend the logic behind it.

But when it comes to make my own algorithm with templates... my mind just blows up !&$%"£("

This is a very stupid try:

http://coliru.stacked-crooked.com/a/5f221d86ad0bc351

I'm angry because I know that, at least a little bit, my logic is correct but I can't get it to work in the way I want with templates.

(before that code, I made another little draft where the case assign_partial used to work...)

My mind is fucked up right now
Last edited on Oct 6, 2016 at 1:46pm
Oct 6, 2016 at 2:02pm
> error: no matching function for call to 'assign'
> assign_helper<T, N, POS + 1>::assign(vec, std::forward<REST>(rest)...);
> note: candidate function template not viable: requires at least 2 arguments, but 1 was provided
> static void assign(T(&vec)[N], FIRST&& first, REST&&... rest)

The problem is easy to dignose: If the number of initialisers is less than the size of the array, at some point rest would become an empty pack, and we can't call
static void assign(T(&vec)[N], FIRST&& first, REST&&... rest)
which requires at least one more argument in addition to vec.

Once we have identified the problem, the solution is simplicity itself:
overload assign so that it can be called with just the vec argument.

Incrementally building on the earlier code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include <iostream>
#include <iomanip>

namespace detail_
{
    /////////////////////////////////////// assign_helper ////////////////////////////////
    template < typename T, std::size_t N, std::size_t POS = 0 > struct assign_helper
    {
        template< typename FIRST, typename... REST > // called when size of the pack is at least one
        static void assign( T (&vec)[N], FIRST&& first, REST&&... rest )
        {
             vec[POS] = std::forward<FIRST>(first) ;
             assign_helper<T,N,POS+1>::assign( vec, std::forward<REST>(rest)... ) ;
        }

        static void assign( T (&)[N] ) {} // do nothing: called when the parameter pack is empty
    };

    template < typename T, std::size_t N > struct assign_helper< T, N, N > // out of range: do nothing
    { template < typename... REST > static void assign( T (&)[N], const REST&... ) {} };
    ////////////////////////////////////////////////////////////////////////////////////////


    ///////////////////////////////// missing argument privider ////////////////////////////
    template < typename T, std::size_t N, bool NO_MISSING_ARGS, typename... U >
    struct missing_argument_provider // NO_MISSING_ARGS == true: no missing argument
    {
        static void assign( T (&vec)[N], U&&... pack )
        { detail_::assign_helper< T, N >::assign( vec, std::forward<U>(pack)... ) ; }
    };

    template < typename T, std::size_t N, typename... U >
    struct missing_argument_provider< T, N, false, U... > // NO_MISSING_ARGS == false: at least one missing argument
    {
        static void assign( T (&vec)[N], U&&... pack ) // add one more argument, and try again
        { missing_argument_provider< T, N, ( sizeof...(U) >= (N-1) ), U..., T >::assign( vec, std::forward<U>(pack)..., T{} ) ; }
    };
    ////////////////////////////////////////////////////////////////////////////////////////
}

// assign values from the pack, if pack has fewer values than required, leave the remaining unchanged
template < typename T, std::size_t N, typename... U > void assign( T (&vec)[N], U&&... pack )
{ detail_::assign_helper< T, N >::assign( vec, std::forward<U>(pack)... ) ; }

constexpr struct value_initialise_tail_t {} value_initialise_tail{} ;
// assign values from the pack, if pack has fewer values than required, value initialise the remaining
template < typename T, std::size_t N, typename... U > void assign( value_initialise_tail_t, T (&vec)[N], U&&... pack )
{ detail_::missing_argument_provider< T, N, ( sizeof...(U) >= N ), U... >::assign( vec, std::forward<U>(pack)... ) ; }

int main()
{
    int a[9]{} ;
    const auto print_a = [&a] { for( int v : a ) std::cout << std::setw(2) << v << ' ' ; std::cout << '\n' ; };

    assign( a, 1, 2, 3, 4, 5, 6, 7, 8, 9 ) ;
    print_a() ; //  1  2  3  4  5  6  7  8  9

    assign( a, -1, 2, -3, 4, -5, 66, 77, 88, 99, 100, -111, 212 ) ;
    print_a() ; // -1  2 -3  4 -5 66 77 88 99

    assign( a, 1, -2, 3, -4, 5 ) ;
    print_a() ; //  1 -2  3  -4  5 66 77 88 99

    assign( value_initialise_tail, a, -4, -5, -6, -7, -8 ) ;
    print_a() ; // -4 -5 -6 -7 -8  0  0  0  0

    assign(a) ;
    print_a() ; // -4 -5 -6 -7 -8  0  0  0  0

    assign( value_initialise_tail, a ) ;
    print_a() ; //  0  0  0  0  0  0  0  0  0
}

http://coliru.stacked-crooked.com/a/3e257bdf8269b209
Oct 6, 2016 at 2:37pm
What can I say...

I really thank you for the time spent and for your very nice explanations.

Thank you for providing this awful-looking code with Templates, which is a programming skill I haven't completely mastered yet, and thanks to you I may have a chance to.

Thank you for being patient and kind.

Best wishes to you dude :)

Last edited on Oct 6, 2016 at 2:38pm
Topic archived. No new replies allowed.