0

So I wrote a very simplified example of what I'm trying to accomplish which in this case is a simple "matrix" struct. As expected this program will compile and run (gcc 9.4 on ubuntu 20.04) and provide a valid result for the matrix D, however the problem is that I can't use references as arguments for the functions and overloaded operators (I've tried and it just spits out undefined references) and which will result in a ton of copies of the object "matrix" to evaluate matrix<int> D = C^(ExpFunc(A + B)).

So the question is: What would be the "correct" or better way to accomplish this? Thank you!!

(before you go all out recommending all those great libs that will handle this way way better and will be more reliable and etc.. I want to do this from scratch to learn and improve my skills and knowledge)

//f.h file
#include <iostream>
#include <vector>
#include <math.h>

template <typename T> struct matrix
{
    std::vector<T> data;
    size_t lines, columns;

    matrix() = default;
    matrix(const size_t L, const size_t C);
    void print();
    
};

//operation A + B = result
template <typename T> matrix<T> operator+(matrix<T> A,  matrix<T> B);
//Hadamard product of A # B
template <typename T> matrix<T> operator^(matrix<T> A,  matrix<T> B);
//element-wise e^x function
template <typename T> matrix<T> ExpFunc(matrix<T> A);
//f.cpp file
#include "f.h"
template <typename T> matrix<T>::matrix(const size_t L, const size_t C){
    matrix<T>::data.resize(L * C);
    matrix<T>::lines = L;
    matrix<T>::columns = C;
}
template matrix<int>::matrix(const size_t L, const size_t C);

template <typename T> void matrix<T>::print(){
    for (size_t i = 1; i < matrix<T>::lines + 1; i++) {
        std::cout << "| ";
        for (size_t j = 1; j < matrix<T>::columns + 1; j++) {
            std::cout << matrix<T>::data[(i - 1) * matrix<T>::columns + (j - 1)] << " ";
        }
        std::cout << "|" << std::endl;
    }
    std::cout << "--------------" << std::endl;
}
template void matrix<int>::print();

template <typename T> matrix<T> ExpFunc(matrix<T> A){
    for (auto& ie : A.data)
        ie = exp(ie);
    return A;
}
template matrix<int> ExpFunc(matrix<int> A);

//operation A + B = result
template <typename T> matrix<T> operator+(matrix<T> A,  matrix<T> B){
    for (size_t i = 0; i < A.data.size(); i++){
        A.data[i] += B.data[i];
    }
    return A;
}
template matrix<int> operator+( matrix<int> A,  matrix<int> B);

template <typename T> matrix<T> operator^( matrix<T> A,  matrix<T> B){
    for (size_t i = 0; i < A.data.size(); i++){
        A.data[i] *= B.data[i];
    }
    return A;
}
template matrix<int> operator^( matrix<int> A,  matrix<int> B);

And the main.cpp file

#include "f.h"

int main(){   
    matrix<int> A(3,3), B(3,3), C(3,3);
    A.data = std::vector<int>{0,0,0,1,1,1,2,2,2};
    B.data = std::vector<int>{9,1,8,2,7,3,6,5,4};
    C.data = std::vector<int>{1,2,3,4,5,6,7,8,9};
    matrix<int> D = C^(ExpFunc(A + B));
    D.print();
    return 0;
}
  • `operator+(matrix A, matrix B);` is 2 unnecessary copies. Pass parameters by const reference. You might want to read about expression templates – 463035818_is_not_an_ai Aug 01 '22 at 07:13
  • 1
    "(I've tried and it just spits out undefined references) " then you made a mistake. There is nothing wrong with passing the parameters by const reference – 463035818_is_not_an_ai Aug 01 '22 at 07:15
  • 1
    The way your `operator +` is implemented, `A` must be by-value, but `B` can be by const-ref. The same is true for `operator ^` . That's probably one of your problems you didn't elaborate on. For `ExpFunc`, there is no option, as-coded it *must* be by-value. – WhozCraig Aug 01 '22 at 07:19
  • You might want to review [ask]. Putting the question before the code both helps keep readers' interest and sometimes forces you to write out what you mean instead of hoping others see what you see in your code. – JaMiT Aug 01 '22 at 07:33
  • Does this answer your question? [Why can templates only be implemented in the header file?](https://stackoverflow.com/questions/495021/why-can-templates-only-be-implemented-in-the-header-file) – JaMiT Aug 01 '22 at 07:35
  • "I've tried and it just spits out undefined references". So why not ask about *this* problem? – n. m. could be an AI Aug 01 '22 at 08:40

1 Answers1

0

This compiles and should work fine:

#include <vector>


template <typename T> struct matrix
{
    std::vector<T> data;
    std::size_t lines, columns;

    matrix() = default;
    matrix(const std::size_t L, const std::size_t C);
    void print();
};

template <typename T>
matrix<T> operator+(const matrix<T>& A, const matrix<T>& B);

template <typename T>
matrix<T> operator+(const matrix<T>& A, const matrix<T>& B)
{
    matrix<T> rtrn(A.lines, A.columns);
    for (std::size_t i = 0; i < A.data.size(); i++)
        rtrn.data[i] = A.data[i] + B.data[i];
    return rtrn;
}

You could also keep the current pattern of using the left input as a copy for the output. It is reasonably efficient but may increase code size.

template <typename T>
matrix<T> operator+(matrix<T> A, const matrix<T>& B);

template <typename T>
matrix<T> operator+(matrix<T> A, const matrix<T>& B)
{
    for (std::size_t i = 0; i < A.data.size(); i++)
        A.data[i] += B.data[i];
    return A;
}

Normally, I would advise against this pattern because it invokes a copy that could instead be folded into the computation (reducing the total number of memory operations). However, you use std::vector internally and that always zero-initializes its elements. Therefore compared to the version above, the copy only replaces one memset with a memcpy. Not much worse.

Further reduction in allocations

If you want to reduce the number of temporary vectors further in chained operations such as C^(ExpFunc(A + B)) you can change the operations to work on expression objects, similar to what Eigen does.

Homer512
  • 9,144
  • 2
  • 8
  • 25
  • Using the left input as a copy for the output was just "since I'm getting this by-value might as well use it!" but in my actual code the return object will be constructed by the operator. And the whole point of the question was to make chained operations as efficient as possible, I'll look into that, thank you! – Eloi Marks Aug 01 '22 at 17:54