Why avoid constant template parameter

T

Tony Johansson

Hello Experts!

What does this mean actually.

If you have a template with a type and non-type template argument, say, like
this
template<typename T, int a>
class Array {. . .};

then A<int, 1> and A<int, 2> are different types. Now, if the A template
had some code (implementation), then if it's used, it would be
_instantiated_ for
every template instantiation, which may lead to code "bloat".

I mean that in main below I instansiate 10 objects using constant parameter
to the
class template which is not good.
So this will mean that I will create 10 different types because I create 10
objects.
We can also assume that the implamentation code is quite large in class
template Array.

At what situations will the object file be great on account of using
constant template parameter.
Give me some good example on this.
It's only the instansiated objects that really allocate memory.
Will it be big in this example with 10 objects?
If I instead had instansiated 100 object of this class template array
would the object file be big then.

I can't see why the object code would expand just because we have constant
template parameter. It's only when you instansiate object by value you
allocate memory which will cause the object file to expand.

int main()
{
Array<int,1> a1;
Array<int,2> a2;
Array<int,3> a3;
Array<int,4> a4;
Array<int,5> a5;
Array<int,6> a6;
Array<int,7> a7;
Array<int,8> a8;
Array<int,9> a9;
Array<int,10> a10;

return 0;
}

Many thanks
 
G

Gianni Mariani

Tony Johansson wrote:
....
I can't see why the object code would expand just because we have constant
template parameter. It's only when you instansiate object by value you
allocate memory which will cause the object file to expand.


Consider this:


template<typename T, int a>
class Array
{
Array()
{
... really big complicated, huge even, code ...
... and then some ...
}

};


Your code could instantiate the Array constructor 10 times. That might
be 10 times more code than if you did:


template<typename T>
class Array
{
Array( int a )
{
... really big complicated, huge even, code ...
... and then some ...
}

};


However, if your code is small and inlineable, it would probably make
very little difference.
 
T

Tony Johansson

Gianni Mariani said:
Tony Johansson wrote:
...


Consider this:


template<typename T, int a>
class Array
{
Array()
{
... really big complicated, huge even, code ...
... and then some ...
}

};


Your code could instantiate the Array constructor 10 times. That might be
10 times more code than if you did:
template<typename T>
class Array
{
Array( int a )
{
... really big complicated, huge even, code ...
... and then some ...
}

};

I can't understand why it should be any major difference
What is simalar between these two well both instansiate the same number of
objects
in this example 10. What is different between these two well in the one
example
we instansiate each object by passing an int to the constructor. In the
other example we instansiate
each object after the class template has been specilized to an int in this
case.

So what do you mean when you say this. I can't follow your minds here.
Your code could instantiate the Array constructor 10 times. That might
be 10 times more code than if you did:


template<typename T>
class Array
{
Array( int a )
{
... really big complicated, huge even, code ...
... and then some ...
}

};

//Tony




In this example that you showed me
template<typename T>
class Array
{
Array( int a )
{
... really big complicated, huge even, code ...
... and then some ...
}
};
 
D

Dave Townsend

Tony Johansson said:
Hello Experts!

What does this mean actually.

If you have a template with a type and non-type template argument, say, like
this
template<typename T, int a>
class Array {. . .};

then A<int, 1> and A<int, 2> are different types. Now, if the A template
had some code (implementation), then if it's used, it would be
_instantiated_ for
every template instantiation, which may lead to code "bloat".

I mean that in main below I instansiate 10 objects using constant parameter
to the
class template which is not good.
So this will mean that I will create 10 different types because I create 10
objects.
We can also assume that the implamentation code is quite large in class
template Array.

At what situations will the object file be great on account of using
constant template parameter.
Give me some good example on this.
It's only the instansiated objects that really allocate memory.
Will it be big in this example with 10 objects?
If I instead had instansiated 100 object of this class template array
would the object file be big then.

I can't see why the object code would expand just because we have constant
template parameter. It's only when you instansiate object by value you
allocate memory which will cause the object file to expand.

int main()
{
Array<int,1> a1;
Array<int,2> a2;
Array<int,3> a3;
Array<int,4> a4;
Array<int,5> a5;
Array<int,6> a6;
Array<int,7> a7;
Array<int,8> a8;
Array<int,9> a9;
Array<int,10> a10;

return 0;
}

Many thanks

I think this question has a design consideration:

Pattern (a)
template<typename T, int a>
class Array {. . .};

where the int a parameter might specify the size of the array of "T'".s

Pattern (b).
template<typename T >
class Array {. . .};
( the size of Array is determined at runtime )

In pattern(a), we are "hardcoding" the size of the instance of Array at
compile time. In situation b), the
instance of Array can be dynamically sized, say, as a constructor parameter.

Now if you instantiate 10 classes A<char, 1>, A<char, 2), etc based upon
Pattern (a),
you will indeed generate 10 separate pieces of code, each similar but
slightly different.
In Pattern ( b), only one set of code will be generated for each distinct
type generated by
Array<>.

Pattern (a) might be desirable since this might be more efficient at
runtime,
you'll give up a bit of code bloat for super fast implementations of your
classes,
whereas b) might be acceptable if runtime efficiency is not the highest
priority, but we
would like to control the size of generated code.

I've developed code using the pattern (a) in a digital simulation system I
worked on.
This pattern was applied in a number of circumstances, but here's one as an
example: Logic "gates" have 1,2,3,4, etc inputs, we found that specializing
code
for 1,2,3,4, etc inputs using templates allowed us to develop very
efficient code to
evaluate logic functions over the general purpose code which looped over
each input
value & computed a result.

Of course, we accepted the necessary code bloat as a trade off for higher
runtime performance.


In conclusion, patterns (a) & (b) have their place in the C++ developers
toolbox,
just be aware of the benefits and consequences.

dave
 
T

Tony Johansson

Dave Townsend said:
I think this question has a design consideration:

Pattern (a)
template<typename T, int a>
class Array {. . .};

where the int a parameter might specify the size of the array of "T'".s

Pattern (b).
template<typename T >
class Array {. . .};
( the size of Array is determined at runtime )

In pattern(a), we are "hardcoding" the size of the instance of Array at
compile time. In situation b), the
instance of Array can be dynamically sized, say, as a constructor
parameter.

Now if you instantiate 10 classes A<char, 1>, A<char, 2), etc based upon
Pattern (a),
you will indeed generate 10 separate pieces of code, each similar but
slightly different.
In Pattern ( b), only one set of code will be generated for each distinct
type generated by
Array<>.

Still I can't understand why the object code will be greater if you use
constant class template parameter.

In this example Now if you instantiate 10 classes A<char, 1>, A<char, 2),
etc based upon
Pattern (a),
you will indeed generate 10 separate pieces of code, each similar but
slightly different.
Do you here mean 10 objects each instanciated from its own class because we
will create 10 different but
slightly similar classes.

On the other hand in pattern(b) where you pass an int to a constructor just
for being able tell the size
for the array that exist in the concrete class I mean you do
Array a1(1), Array a2(2), Array a3(3), Arraya a4(4) until Array
a10(10) .
You will also here create 10 object just the same as you did for pattern(a).

Well I can't see any major difference.
In pattern(a) you create classes and then objects based on the created
class. For example the size for the array that exist within each generated
class is defined at compile time The constant class template parameter tell
the size for the array within the class template.

In pattern(b) you also create objects but from a concrete class. The array
that exist within this concrete class is passed at runtime. But you also
here create 10 different object each with a different size of
the array that exist within in the concrete will here.

As a summary in both cases for pattern(a) and pattern(b) you vill create 10
different objects with
the size of the array located in the class template and in the concrete
class specified in different ways.
In pattern(a) by using the constant class parameter and for pattern(b)
specified at run-time by passing a value to the constructor.

Pattern(a) will have 10 different objects called a1,a2,a3,a4,a5,a6,a7,a8,a9
and a10 the number tell the size of the array located in the class template.
The definition of the array located in the class template is
T array[size]; So Array <int,7> a7; will create an int array with the size
of 7.


Pattern(b) will have 10 different objects called a1, a2, a3, a4, a5, a6, a7,
a8,a9,a10 the size of the array will be passed in the constructor. So for
example Array a7(7); will crete an object called a7 with the size of the
local array defined in the concrete as int array[7];

//Tony
 
S

Serge Paccalin

Le dimanche 21 août 2005 à 10:08:00, Tony Johansson a écrit dans
comp.lang.c++ :
Still I can't understand why the object code will be greater if you use
constant class template parameter.

In this example Now if you instantiate 10 classes A<char, 1>, A<char, 2),
etc based upon
Pattern (a),
you will indeed generate 10 separate pieces of code, each similar but
slightly different.
Do you here mean 10 objects each instanciated from its own class because we
will create 10 different but
slightly similar classes.

Yes, there will be 10 slightly different constructors, 10 slightly
different destructors, and so on. The 10 calls in your main() function
call 10 different constructors.

Class1::Class1() { }
Class2::Class2() { }
Class3::Class3() { }
....

int main()
{
// calls Class1::Class1();
// calls Class2::Class2();
// calls Class3::Class3();
....
}
On the other hand in pattern(b) where you pass an int to a constructor just
for being able tell the size
for the array that exist in the concrete class I mean you do
Array a1(1), Array a2(2), Array a3(3), Arraya a4(4) until Array
a10(10) .
You will also here create 10 object just the same as you did for pattern(a).

10 objects of the same class. There is only one constructor, one
destructor, and so on. The 10 calls in your main() function call the
same function.

Class::Class(int n) { }

int main()
{
// calls Class::Class(1);
// calls Class::Class(2);
// calls Class::Class(3);
....
}

--
___________ 21/08/2005 10:41:07
_/ _ \_`_`_`_) Serge PACCALIN -- sp ad mailclub.net
\ \_L_) Il faut donc que les hommes commencent
-'(__) par n'être pas fanatiques pour mériter
_/___(_) la tolérance. -- Voltaire, 1763
 
K

Kai-Uwe Bux

Tony said:
"Dave Townsend" <[email protected]> skrev i meddelandet [snip]
Pattern (a)
template<typename T, int a>
class Array {. . .};

where the int a parameter might specify the size of the array of "T'".s

Pattern (b).
template<typename T >
class Array {. . .};
( the size of Array is determined at runtime )

In pattern(a), we are "hardcoding" the size of the instance of Array at
compile time. In situation b), the
instance of Array can be dynamically sized, say, as a constructor
parameter.

Now if you instantiate 10 classes A<char, 1>, A<char, 2), etc based upon
Pattern (a),
you will indeed generate 10 separate pieces of code, each similar but
slightly different.
In Pattern ( b), only one set of code will be generated for each
distinct type generated by
Array<>.

Still I can't understand why the object code will be greater if you use
constant class template parameter. [snip]
in both cases for pattern(a) and pattern(b) you vill create
10 different objects with
the size of the array located in the class template and in the concrete
class specified in different ways.
In pattern(a) by using the constant class parameter and for pattern(b)
specified at run-time by passing a value to the constructor.

Pattern(a) will have 10 different objects called
a1,a2,a3,a4,a5,a6,a7,a8,a9 and a10 the number tell the size of the array
located in the class template. The definition of the array located in the
class template is
T array[size]; So Array <int,7> a7; will create an int array with the
size of 7.


Pattern(b) will have 10 different objects called a1, a2, a3, a4, a5, a6,
a7, a8,a9,a10 the size of the array will be passed in the constructor. So
for example Array a7(7); will crete an object called a7 with the size of
the local array defined in the concrete as int array[7];

//Tony

You need to distinguish:

a) code size (generated by the compiler)
b) memory consumption at run time

Code Size depends on the number of different *types* (Classes) that you use
in your program. Since the instantiations of the Array<> template are
different classes the compiler will generate code for dealing with each of
them. That determines code size.

Memory consumption at run time depends on how many *objects* your program
creates during execution.

Thus, the fact that in both patterns the same number of objects are created
at run time is immaterial as far as code size is concerned.


Best

Kai-Uwe Bux

ps: If you want to torture your compiler and produce a really big object
file, compile the following into somefile.o and look at the file size. Do
not be surprised to find a really big object file: this snippet produces
about 10000 instantiations and dumps code for each of it into the object
file. Note that no objects are created at all.

// addition_table.cc

template < unsigned long A_MAX, unsigned long B_MAX >
struct arithmetic {

enum { sum = A_MAX + B_MAX };

static
unsigned long inc ( unsigned long a ) {
// return B_MAX + a
if ( a == A_MAX ) {
return( arithmetic< A_MAX, B_MAX >::sum );
}
return( arithmetic< A_MAX-1, B_MAX >::inc( a ) );
}

static
unsigned long add ( unsigned long a, unsigned long b ) {
if ( a == A_MAX ) {
return( arithmetic< B_MAX, A_MAX >::inc( b ) );
}
return( arithmetic< A_MAX-1, B_MAX >::add( a, b ) );
}

};

template < unsigned long B_MAX >
struct arithmetic< 0, B_MAX > {

enum { sum = B_MAX };

static
unsigned long inc ( unsigned long a ) {
return( B_MAX );
}

static
unsigned long add ( unsigned long a, unsigned long b ) {
return( b );
}

};


unsigned long add ( unsigned long a, unsigned long b ) {
return( arithmetic<100,100>::add( a, b ) );
}

// end of file
 
O

Old Wolf

Kai-Uwe Bux said:
Code Size depends on the number of different *types* (Classes) that you use
in your program. Since the instantiations of the Array<> template are
different classes the compiler will generate code for dealing with each of
them. That determines code size.

It seems to me that because of the "as-if" rule, the compiler
only needs to generate one function body and it can accept
the int as if it were a normal function parameter.

The C++ standard doesn't say that the compiler needs to
generate different blocks of code for different functions, eg:

void foo() { baz(); qux(); }
void bar() { baz(); qux(); }

A clever compiler/linker would only generate one block of code,
and calls to foo() and bar() would both go to it, assuming
that the program never tries to compare the addresses of
the two functions.
 
K

Kai-Uwe Bux

Old said:
It seems to me that because of the "as-if" rule, the compiler
only needs to generate one function body and it can accept
the int as if it were a normal function parameter.

Agreed, the compiler is *allowed* to do that. And it will work in many
cases, but what about:

template < typename T, std::size_t N >
struct array {

T data [N];

...

}; //

Actually, I guess a compiler could actually handle this as a parametrized
allocation. Of course, calls to sizeof() would need to be handled
carefully. Anyway, I am too tired right now to think of something to really
force code generation for each instantiation. However, I would venture the
conjecture that most implementations will just mindlessly dump code for all
classes created.


Best

Kai-Uwe Bux
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,263
Messages
2,571,064
Members
48,769
Latest member
Clifft

Latest Threads

Top