James C
James C

Reputation: 919

Getting results from functions with 'void' return type, while resulting variable is one of the input arguments - C++

I got this library of mathematical routines ( without documentation ) to work on some task at college. The problem I have with it is that all of its functions have void return type, although these functions call one another, or are part of another, and the results of their computations are needed.

This is a piece of ( simplified ) code extracted from the libraries. Don't bother about the mathematics in code, it is not significant. Just passing arguments and returning results is what puzzles me ( as described after code ) :

// first function

void vector_math // get the (output) vector we need
 ( 
   double inputV[3], // input vector
   double outputV[3] // output vector
 )
 {
      // some variable declarations and simple arithmetics
      // .....
      //

      transposeM(matrix1, matrix2, 3, 3 ); // matrix2 is the result

      matrixXvector( matrix2, inputV, outputV) // here you get the result, outputV
 }

////////

// second function

 void transposeM // transposes a matrix 
    ( 
      std::vector< std::vector<double> > mat1, // input matrix
      std::vector< std::vector<double> > &mat2, // transposed matrix
      int mat1rows, int mat1columns 
    )
   {         
   int row,col;

    mat2.resize(mat1columns);  // rows
     for (std::vector< std::vector<double> >::iterator it=mat2.begin(); it !=mat2.end();++it)
          it->resize(mat1rows);

 for (row = 0; row < mat1rows; row++)
     {
     for (col = 0; col < mat1columns; col++)
         mat2[col][row] = mat1[row][col];
     }
   }

   ////////

   // third function

   void matrixXvector // multiply matrix and vector
    (
          std::vector< std::vector<double> > inMatrix,
          double inVect[3],
          double outVect[3]
    )
      {
     int row,col,ktr;

     for (row = 0; row <= 2; row++)
      {
     outVect[row]= 0.0;
     for (ktr = 0; ktr <= 2; ktr++)
         outVect[row]= outVect[row] + inMatrix[row][ktr] * inVect[ktr];
       }
   }

So "vector_math" is being called by the main program. It takes inputV as input and the result should be outputV. However, outputV is one of the input arguments, and the function returns void. And similar process occurs later when calling "transposeM" and "matrixXvector".

Why is the output variable one of the input arguments ? How are the results being returned and used for further computation ? How this kind of passing and returning arguments works ?

Since I am a beginner and also have never seen this style of coding, I don't understand how passing parameters and especially giving output works in these functions. Therefore I don't know how to use them and what to expect of them ( what they will actually do ). So I would very much appreciate an explanation that will make these processes clear to me.

EXTRA :

Thank you all for great answers. It was first time I could barely decide which answer to accept, and even as I did it felt unfair to others. I would like to add an extra question though, if anyone is willing to answer ( as a comment is enough ). Does this "old" style of coding input/output arguments have its name or any other expression with which it is referred ?

Upvotes: 2

Views: 3288

Answers (3)

Brad
Brad

Reputation: 3510

Baum's answer is accurate, but perhaps not as detailed as a C/C++ beginner would like.

The actual argument values that go into a function are always passed by value (i.e. a bit pattern) and cannot be changed in a way that is readable by the caller. HOWEVER - and this is the key - those bits in the arguments may in fact be pointers (or references) that don't contain data directly, but rather contain a location in memory that contains the actual value.

Examples: in a function like this:

void foo(double x, double output) { output = x ^ 2; }

naming the output variable "output doesn't change anything - there is no way for the caller to get the result.

But like this:

void foo(double x, double& output) { output = x ^ 2; }

the "&" indicates that the output parameter is a reference to the memory location where the output should be stored. It's syntactic sugar in C++ that is equivalent to this 'C' code:

void foo(double x, double* pointer_to_output) { *pointer_to_output = x ^ 2; }

The pointer dereference is hidden by the reference syntax but the idea is the same.

Arrays perform a similar syntax trick, they are actually passed as pointers, so

void foo(double x[3], double output[3]) { ... }

and

void foo(double* x, double* output) { ... }

are essentially equivalent. Note that in either case there is no way to determine the size of the arrays. Therefore, it is generally considered good practice to pass pointers and lengths:

void foo(double* x, int xlen, double* output, int olen);

Output parameters like this are used in multiple cases. A common one is to return multiple values since the return type of a function can be only a single value. (While you can return an object that contains multiple members, but you can't return multiple separate values directly.) Another reason why output parameters are used is speed. It's frequently faster to modify the output in place if the object in question is large and/or expensive to construct.

Another programming paradigm is to return a value that indicates the success/failure of the function and return calculated value(s) in output parameters. For example, much of the historic Windows API works this way.

Upvotes: 2

Konrad Zemek
Konrad Zemek

Reputation: 111

An array is a low-level C++ construct. It is implicitly convertible to a pointer to the memory allocated for the array.

int a[] = {1, 2, 3, 4, 5};
int *p = a; // a can be converted to a pointer
assert(a[0] == *a);
assert(a[1] == *(a + 1));
assert(a[1] == p[1]);
// etc.

The confusing thing about arrays is that a function declaration void foo(int bar[]); is equivalent to void foo(int *bar);. So foo(a) doesn't copy the array a; instead, a is converted to a pointer and the pointer - not the memory - is then copied.

void foo(int bar[]) // could be rewritten as foo(int *bar)
{
    bar[0] = 1; // could be rewritten as *(bar + 0) = 1;
}

int main()
{
    int a[] = {0};
    foo(a);
    assert(a[0] == 1);
}

bar points to the same memory that a does so modifying the contents of array pointed to by bar is the same as modifying the contents of array a.

In C++ you can also pass objects by reference (Type &ref;). You can think of references as aliases for a given object. So if you write:

int a = 0;
int &b = a;
b = 1;
assert(a == 1);

b is effectively an alias for a - by modifying b you modify a and vice versa. Functions can also take arguments by reference:

void foo(int &bar)
{
    bar = 1;
}

int main()
{
    int a = 0;
    foo(a);
    assert(a == 1);
}

Again, bar is little more than an alias for a, so by modifying bar you will also modify a.


The library of mathematical routines you have is using these features to store results in an input variable. It does so to avoid copies and ease memory management. As mentioned by @Baum mit Augen, the method can also be used as a way to return multiple values.

Consider this code:

vector<int> foo(const vector<int> &bar)
{
    vector<int> result;
    // calculate the result
    return result;
}

While returning result, foo will make a copy of the vector, and depending on number (and size) of elements stored the copy can be very expensive.

Note: Most compilers will elide the copy in the code above using Named Return Value Optimization (NRVO). In general case, though, you have no guarantee of it happening.

Another way to avoid expensive copies is to create the result object on heap, and return a pointer to the allocated memory:

vector<int> *foo(const vector<int> &bar)
{
    vector<int> *result = new vector<int>;
    // calculate the result
    return result;
}

The caller needs to manage the lifetime of the returned object, calling delete when it's no longer needed. Faililng to do so can result in a memory leak (the memory stays allocated, but effectively unusable, by the application).

Note: There are various solutions to help with returning (expensive to copy) objects. C++03 has std::auto_ptr wrapper to help with lifetime management of objects created on heap. C++11 adds move semantics to the language, which allow to efficiently return objects by value instead of using pointers.

Upvotes: 1

Baum mit Augen
Baum mit Augen

Reputation: 50043

This is an "old" (but still popular) style of returning certain or multiple values. It works like this:

void copy (const std::vector<double>& input, std::vector<double>& output) {
    output = input;
}

int main () {
    std::vector<double> old_vector {1,2,3,4,5}, new_vector;
    copy (old_vector, new_vector); // new_vector now copy of old_vector
}

So basically you give the function one or multiple output parameter to write the result of its computation to.

If you pass input parameters (i.e. you don't intend to change them) by value or by const reference does not matter, although passing read only arguments by value might be costly performance-wise. In the first case, you copy the input object and use the copy in the function, in the latter you just let the function see the original and prevent it from being modified with the const. The const for the input parameters is optional, but leaving it out allows the function to change their values which might not be what you want, and inhibits passing temporaries as input.

The input parameter(s) have to be passed by non-const reference to allow the function to change it/them.

Another, even older and "C-isher" style is to passing output-pointer or raw-arrays, like the first of your functions does. This is potentially dangerous as the pointer might not point to a valid piece of memory, but still pretty wide spread. It works essentially just like the first example:

// Copies in to int pointed to by out
void copy (int in, int* out) {
    *out = in;
}

// Copies int pointed to by in to int pointed to by out
void copy (const int* in, int* out) {
    *out = *in;
}

// Copies length ints beginning from in to length ints beginning at out
void copy (const int* in, int* out, std::size_t length) {
    // For loop for beginner, use std::copy IRL:
    // std::copy(in, in + length, out);
    for (std::size_t i = 0; i < length; ++i)
        out[i] = in[i];
}

The arrays in your first example basically work like pointers.

Upvotes: 2

Related Questions