taking arrays/containers as member function arguments of an API

H

Hicham Mouline

hello,

In the main class of the API of a library, we have a function

class M {
public:
void Calculate( CalcStatus* status, double* result, int tag, Operation
operation );
};

this calculates 1 result, returns a status for 1 tag and 1 operation.
We would like to extend this API with more functions to calculate N
results/statues for N tags and 1 operation,
M results/statues for 1 tag for M operations
M*N results/statues for N tags and M operations for each tag.

M and N are runtime variables. M is of the order of 10. N is of the order of
up to 1000
The question is whether to native arrays or std::vector. Performance is
especially relevant

// This API assumes statuses and resultes arrays are allocated by caller
with appropriate sizes
class M {
public:
void Calculate( CalcStatus statuses[], double results[], const int tags[],
size_t N, Operation operation );
void Calculate( CalcStatus statuses[], double results[], int tag, const
Operation operations[], size_t M);
// Have caller interpret statues and results linearily: (m,n) result is
m*M + n index for e.g.
void Calculate( CalcStatus statuses[], double results[], const int tags[],
size_t N, const Operation operations[], size_t M );
};

Also, the question of clarity/elegance/cleanness arises. This API is to be
used by 3rd party users.

of

// size determined from the const input vectors
class M {
public:
void Calculate( vector<CalcStatus>& statuses, vector<double>& results,
const vector<int>& tags, Operation operation );
void Calculate( vector<CalcStatus>& statuses, vector<double>& results, int
tag, const vector<Operation>& operations);
// use boost::multi_array in this last case
void Calculate( vector<CalcStatus>& statuses, vector<double>& results,
const vector<int>& tags, const vector<Operation>& operations);
};
// perhaps template these function with a template argument that is a
template itself (STL containers) to not force the user to provide a vector

Are there any style conventions re such a case?

regards,
 
A

alfps

hello,

In the main class of the API of a library,

"main class"?

we have a function

class M {
public:
   void Calculate( CalcStatus* status, double* result, int tag, Operation
operation );

};

this calculates 1 result, returns a status for 1 tag and 1 operation.

This seems to mean the function returns a CalcStatus and a double.

Using out-parameters is OK.

However, unless the function supports null-pointers for those
parameters, the possibility of incorrect usage (and possibility of
time wasted on determining correct usage) is greatly reduced by using
pass by reference.

We would like to extend this API with more functions to calculate N
results/statues for N tags and 1 operation,
M results/statues for 1 tag for M operations
M*N results/statues for N tags and M operations for each tag.

The question is, does such wrapping simplify or complicate the client
code?

In other words, what is the perceived advantage, the reason why this
is deemed desirable?

M and N are runtime variables. M is of the order of 10. N is of the order of
up to 1000
The question is whether to native arrays or std::vector. Performance is
especially relevant

For performance nothing beats a simple loop in the client code.

That also provides the greatest flexibility.

E.g., it may be that the client doesn't need all those data points
stored anywhere, but just uses one pair of values at a time. For
another example, error/failure handling may depend on which
computation. And so on.

:)

// This API assumes statuses and resultes arrays are allocated by caller
with appropriate sizes
class M {
public:
  void Calculate( CalcStatus statuses[], double results[], const int tags[],
size_t N, Operation operation );
  void Calculate( CalcStatus statuses[], double results[], int tag, const
Operation operations[], size_t M);
  // Have caller interpret statues and results linearily: (m,n) result is
m*M + n index for e.g.
  void Calculate( CalcStatus statuses[], double results[], const int tags[],
size_t N, const Operation operations[], size_t M );

};

Also, the question of clarity/elegance/cleanness arises. This API is to be
used by 3rd party users.

of

// size determined from the const input vectors
class M {
public:
  void Calculate( vector<CalcStatus>& statuses, vector<double>& results,
const vector<int>& tags, Operation operation );
  void Calculate( vector<CalcStatus>& statuses, vector<double>& results, int
tag, const vector<Operation>& operations);
 // use boost::multi_array in this  last case
  void Calculate( vector<CalcStatus>& statuses, vector<double>& results,
const vector<int>& tags, const vector<Operation>& operations);};

// perhaps template these function with a template argument that is a
template itself (STL containers) to not force the user to provide a vector

Are there any style conventions re such a case?

General: make the client code simple, provide flexibility, make it
hard to use incorrectly.

Instead of parallel (logical) arrays I would probably choose to
combine a status and corresponding 'double' result in a class type
object.

And instead of passing (logical) arrays it will provide greater
flexibility to provide some callback mechanism, e.g. iterators. If the
"efficiency" is with respect to memory usage then that may also
increase efficiency. Otherwise it may be in conflict with the
efficiency goal.

However, consider whether typical client code is really simplified by
having these wrapper functions.

One way of going about it is to take some typical client code, re-
express it in an "ideal" simple way, and then consider whether and how
that can be implemented efficiently.


Cheers & hth.,

- Alf
 
J

James Kanze

"main class"?

I think he means "main" in the general sense of principle, and
not in the sense of the "main" function.
This seems to mean the function returns a CalcStatus and a double.
Using out-parameters is OK.

But not very idiomatic. In this case, I'd probably return a
Fallible (but my implementation of Fallible supports extended
status codes); returning status and using an out parameter for
result is also very common.
However, unless the function supports null-pointers for those
parameters, the possibility of incorrect usage (and
possibility of time wasted on determining correct usage) is
greatly reduced by using pass by reference.
The question is, does such wrapping simplify or complicate the
client code?
In other words, what is the perceived advantage, the reason
why this is deemed desirable?
For performance nothing beats a simple loop in the client
code.

Not even a simple loop in a template function? The "idiomatic"
solution would probably be something like:

struct Result // More likely something more
// complicated, perhaps a Fallible.
// The standard likes std::pair for
// this, but that's really bad
// engineering.
{
CalcStatus status ;
double value ;
} ;

// Constraints:
//
// InputIterator1::value_type convertible to int
// InputIterator2::value_type can be called as a
// function with ... arguments, returning a Result
// OutputIterator supports assignment of a Result
// ---------------------------------------------------------
template< typename InputIterator1, typename InputIterator2,
typename OutputIterator >
void Calculate(
InputIterator1 beginTag,
InputIterator1 endTag,
InputIterator2 beginOperation,
InputIterator2 endOperation,
OutputIterator result )
{
while ( beginTag != endTag ) {
while ( beginOperation != endOperation ) {
*result ++ = (*beginOperation)( ... ) ;
++ beginOperation ;
}
++ beginTag ;
}
}

I'll not argue for or against such a solution; you make some
very good points below concerning usability in client code (and
I'm very far from being convinced that the STL is a model of
good software design). But it is the idiotmatic solution in
modern C++. And it probably won't have any performance
problems; at least no more than any other solution.
That also provides the greatest flexibility.
E.g., it may be that the client doesn't need all those data
points stored anywhere, but just uses one pair of values at a
time. For another example, error/failure handling may depend
on which computation. And so on.

Yes. All very good points, which should be considered at the
design level. In this case, the rule to apply is probably not
to bother with such a function until you find yourself having to
write the loops several times, in more or less the same format.
In which case, refactor. (Of course, if you're working on
library code and don't have access to the client code to see how
it really uses the functions, this is more difficult. But as
Alf more or less says, designing something which will handle all
of the possible use cases is far from obvious.)
 
H

Hicham Mouline

I think he means "main" in the general sense of principle, and
not in the sense of the "main" function. Yep
This seems to mean the function returns a CalcStatus and a double.
Using out-parameters is OK.
But not very idiomatic. In this case, I'd probably return a
Fallible (but my implementation of Fallible supports extended
status codes); returning status and using an out parameter for
result is also very common.
However, unless the function supports null-pointers for those
parameters, the possibility of incorrect usage (and
possibility of time wasted on determining correct usage) is
greatly reduced by using pass by reference.
We adopt stroustrup p99 middle of the page
"reference arguments should be used only where the name of the function
gives a
strong hint that the reference argument is modified"
Whether "Calculate" gives that hint or not is debated inhouse.
It is also debated that "result" is explicit enough to be ok to be used as a
reference.

Actually, the signature is currently:
CalcStatus Calculate( double* result, int tag, Operation operation );
I just changed it to make it similar to the other 3 signatures.


The question is, does such wrapping simplify or complicate the
client code?
In other words, what is the perceived advantage, the reason
why this is deemed desirable?
I realize I regrettable didn't include an essential piece of information.
Function 1 is pure virtual, while 2,3,4 are virtual non pure.
Classes derived from M override 1.
M provides a default implementation for 2,3,4,
but these should be overriden by classes derived from M who
know faster/smarter ways to calculate for the multiple case.

I apologize as this kind of changes everything.

re the templated Calculate below, it is what I was thinking about not using
vector.
However, as it is a virtual function, it can't be templated.
For performance nothing beats a simple loop in the client
code.

Not even a simple loop in a template function? The "idiomatic"
solution would probably be something like:

struct Result // More likely something more
// complicated, perhaps a Fallible.
// The standard likes std::pair for
// this, but that's really bad
// engineering.
{
CalcStatus status ;
double value ;
} ;

// Constraints:
//
// InputIterator1::value_type convertible to int
// InputIterator2::value_type can be called as a
// function with ... arguments, returning a Result
// OutputIterator supports assignment of a Result
// ---------------------------------------------------------
template< typename InputIterator1, typename InputIterator2,
typename OutputIterator >
void Calculate(
InputIterator1 beginTag,
InputIterator1 endTag,
InputIterator2 beginOperation,
InputIterator2 endOperation,
OutputIterator result )
{
while ( beginTag != endTag ) {
while ( beginOperation != endOperation ) {
*result ++ = (*beginOperation)( ... ) ;
++ beginOperation ;
}
++ beginTag ;
}
}

I'll not argue for or against such a solution; you make some
very good points below concerning usability in client code (and
I'm very far from being convinced that the STL is a model of
good software design). But it is the idiotmatic solution in
modern C++. And it probably won't have any performance
problems; at least no more than any other solution.
That also provides the greatest flexibility.
E.g., it may be that the client doesn't need all those data
points stored anywhere, but just uses one pair of values at a
time. For another example, error/failure handling may depend
on which computation. And so on.

Yes. All very good points, which should be considered at the
design level. In this case, the rule to apply is probably not
to bother with such a function until you find yourself having to
write the loops several times, in more or less the same format.
In which case, refactor. (Of course, if you're working on
library code and don't have access to the client code to see how
it really uses the functions, this is more difficult. But as
Alf more or less says, designing something which will handle all
of the possible use cases is far from obvious.)
 
A

alfps

We adopt stroustrup p99 middle of the page
"reference arguments should be used only where the name of the function
gives a
strong hint that the reference argument is modified"
Whether "Calculate" gives that hint or not is debated inhouse.

Argument passing conventions are not (sensibly, with any degree of
rationalism) chosen from routine names.

Nor the opposite way around.

Routine names and argument passing conventions are (rationally) chosen
from basically the same set of higher level considerations about what
the routine does.

If the routine name is then badly chosen, change it.

But don't change argument passing conventions on the basis of routine
names -- that's lunacy.


Cheers & hth.,

- Alf
 
H

Hicham Mouline

On 31 Des, 16:48, "Hicham Mouline" <[email protected]> wrote:

Not even a simple loop in a template function? The "idiomatic"
solution would probably be something like:

struct Result // More likely something more
// complicated, perhaps a Fallible.
// The standard likes std::pair for
// this, but that's really bad
// engineering.
{
CalcStatus status ;
double value ;
} ;

// Constraints:
//
// InputIterator1::value_type convertible to int
// InputIterator2::value_type can be called as a
// function with ... arguments, returning a Result
// OutputIterator supports assignment of a Result
// ---------------------------------------------------------
template< typename InputIterator1, typename InputIterator2,
typename OutputIterator >
void Calculate(
InputIterator1 beginTag,
InputIterator1 endTag,
InputIterator2 beginOperation,
InputIterator2 endOperation,
OutputIterator result )
{
while ( beginTag != endTag ) {
while ( beginOperation != endOperation ) {
*result ++ = (*beginOperation)( ... ) ;
++ beginOperation ;
}
++ beginTag ;
}
}

---------------------------------------------------------------------------------------------------------------------------------
The bottom line is that, as Calculate is a virtual function (to allow for
smarter operation that the default), it cannot be templated.


Thanks,
 
M

Michael DOUBEZ

Hicham Mouline a écrit :
On 31 Des, 16:48, "Hicham Mouline" <[email protected]> wrote:

Not even a simple loop in a template function? The "idiomatic"
solution would probably be something like:
[snip]
template< typename InputIterator1, typename InputIterator2,
typename OutputIterator >
void Calculate(
InputIterator1 beginTag,
InputIterator1 endTag,
InputIterator2 beginOperation,
InputIterator2 endOperation,
OutputIterator result )
{
while ( beginTag != endTag ) {
while ( beginOperation != endOperation ) {
*result ++ = (*beginOperation)( ... ) ;
++ beginOperation ;
}
++ beginTag ;
}
}

It is possible to use iterators in a virtual function by using type
erasure but it has a cost.
 
H

Hicham Mouline

Michael DOUBEZ said:
Hicham Mouline a écrit :
On 31 Des, 16:48, "Hicham Mouline" <[email protected]> wrote:

Not even a simple loop in a template function? The "idiomatic"
solution would probably be something like:
[snip]
template< typename InputIterator1, typename InputIterator2,
typename OutputIterator >
void Calculate(
InputIterator1 beginTag,
InputIterator1 endTag,
InputIterator2 beginOperation,
InputIterator2 endOperation,
OutputIterator result )
{
while ( beginTag != endTag ) {
while ( beginOperation != endOperation ) {
*result ++ = (*beginOperation)( ... ) ;
++ beginOperation ;
}
++ beginTag ;
}
}

It is possible to use iterators in a virtual function by using type
erasure but it has a cost.
 

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
473,734
Messages
2,569,441
Members
44,832
Latest member
GlennSmall

Latest Threads

Top