1
\$\begingroup\$

I am calling matrix implemented with a lambda "lambda matrix". The full working program:

   // g++ prog.cc -std=c++17
   // gcc 8.2.0

     #include <iostream>
     #include <cassert>
     #include <memory>

    [[noreturn]] inline void terror
    (char const * msg_, char const * file_, const unsigned line_)
    {
        assert(msg_ != nullptr);    assert(file_ != nullptr);   assert(line_ > 0);
        ::fprintf(stderr, "\n\nTerminating ERROR:%s\n%s (%d)", msg_, file_, line_);
        exit(EXIT_FAILURE);
    }

      #define DBJ_VERIFY_(x, file, line ) if (false == (x) ) terror( #x ", failed", file, line )
      #define DBJ_VERIFY(x) DBJ_VERIFY_(x,__FILE__,__LINE__)

inline auto print = []( auto const & first_param, auto const & ... params)
{
    std::cout << first_param ;
    // if there are  more params
    if constexpr (sizeof...(params) > 0) {
        // recurse
        print(params...);
    }
    return print;
};

 namespace dbj::mtx
{
    constexpr unsigned short max_cols = 0xFFFF;
    constexpr unsigned short max_rows = 0xFFFF;

    template<typename T>
    auto mtx(unsigned short height_, unsigned short width_)
    {
    #ifdef _MSC_VER
        static_assert(std::is_arithmetic_v <T>, "\n\nstatic assert in:\n\n" __FUNCSIG__ "\n\n\tOnly numbers please!\n\n");
    #else
        static_assert(std::is_arithmetic_v <T>, "\n\nstatic assert in: dbj::mtx::mtx()\t\nOnly numbers please!\n\n");
    #endif
        DBJ_VERIFY(width_ <= max_cols);
        DBJ_VERIFY(height_ <= max_rows);

        return [
            arry = std::make_unique<T[]>(height_ * width_), height_, width_
        ]
        (size_t row_, size_t col_) mutable->T&
        {
                DBJ_VERIFY(row_ <= height_);
                DBJ_VERIFY(col_ <= width_ );
            // arry is moved into here
            return arry[row_ * width_ + col_];
        };
    }
    /*
    visitor signature

    bool (*) (unsigned short rows_, unsigned short columns_,
        auto & mtx_, auto const & visitor_);        

        processing stops on false returned
    */
    auto for_each_cell = []
    (unsigned short rows_, unsigned short columns_,
        auto & mtx_, auto const & visitor_
    )
    {
        assert(columns_ <= max_cols);
        assert(rows_ <= max_rows);

        for (auto j = 0; j < rows_; j++)
            for (auto k = 0; k < columns_; k++)
            {
                if (false == visitor_(mtx_(j, k), j, k)) return;
            }
    };
} // mtx

int main()
{
    using namespace dbj::mtx;
    auto R = 3, C = 3;

    auto mx_1 = mtx<int>(R, C);
    auto mx_2 = mtx<int>(R, C);
    auto mx_3 = mtx<int>(R, C);

    // population
    auto put42 = [](auto & val_, auto row, auto cel) -> bool
    {
        val_ = 42;  return true;
    };
    for_each_cell(R, C, mx_1, put42) ;
    for_each_cell(R, C, mx_2, put42) ;

    // addition
    auto adder_visitor = [&](auto & val_, auto row, auto cel) -> bool
    {
        mx_3(row, cel) = mx_1(row, cel) + mx_2(row, cel);
        return true;
    };

    for_each_cell(R, C, mx_3, adder_visitor);

    // display
        auto printer_visitor = [R,C](auto & val_, short row, short col) 
        {
            static int col_counter_ = 1;
            if ((0 == row)  && (0 == col) ) print("\ndbj::mtx {");
            if (0 == (col % C ))    print("\n\t{");
                // full display: print(" [", row,",",col,"] = ",val_, " ");
                print(" ",val_, " ");
            if ( 0 == ( col_counter_ % C)) print("}");
            if ((R == row+1) && (C == col+1))   print("\n}\n");
            col_counter_++;
            return true;
        };

    print("\nMX 1");
    for_each_cell(R, C, mx_1, printer_visitor);

    print("\nMX 2");
    for_each_cell(R, C, mx_2, printer_visitor);

    print("\nMX 3");
    for_each_cell(R, C, mx_3, printer_visitor);
}

What I like about this approach is simplicity. I also allocate once on the heap in one contigous mem block. So far so good.

Works etc. Obvously one has to "believe in lambdas" to like this. Not a lot, but I do like it.

Also, I would like to measure the perfomance but against what? Some "class-based" classical Matrix?

Also are there some difficult edge-cases or singularities you can spot? I am also slightly puzzled when is that shared pointer going to delete the array it holds.

Please let me know what do you think.

Update

Added the "fast" version, Compile time, pod stack matrix based.

       // the compile time solution
   template<typename T, size_t H, size_t W>
   inline auto fast_mtx () {
        static_assert(std::is_arithmetic_v <T>);
        static_assert(std::is_nothrow_constructible_v <T>);

           constexpr auto height_ = H ;
           constexpr auto width_  = W ;

        static_assert(width_ <= max_cols);
        static_assert(height_ <= max_rows);           

       T arry[H*W]{};

        return [
            arry , height_, width_
        ]
        (size_t row_, size_t col_) mutable->T&
        {
          if constexpr(check_indexes_on_each_call) {
                DBJ_VERIFY(row_ <= height_);
                DBJ_VERIFY(col_ <= width_ );
          }
            // arry is moved into here
            return arry[row_ * width_ + col_];
        };
   } // fast_mtx

Full working program is also here. It is becoming too long to copy paste it here, and too tedious too do it on each change.

\$\endgroup\$
1
\$\begingroup\$

I needed to add a few headers to get this to compile:

#include <cassert>
#include <memory>
#include <type_traits>

There's also use of an undefined identifier __FUNCSIG__ that I needed to remove.


std::size_t is misspelt throughout (implementations are allowed, but not required to declare ::size_t in addition to std::size_t; portable code shouldn't assume so).


Why do we have such small limits on the number of rows and columns? We ought to allow any size we can allocate (i.e. any combination whose product fits in a std::size_t). Note that assert() is the wrong way to check public arguments - remember that it's compiled out when NDEBUG is defined.

At the other end of the scale, should a matrix with zero elements be allowed? I don't see why not (and the zero-sized allocation is perfectly legal), but just to make sure you've considered it...


We could move the pointer into the lambda, so we don't need to increase its reference count:

    return [arry = std::move(arry), rows_, columns_](size_t row_, size_t col_) 
        -> T&
    {
        return arry[row_ * columns_ + col_];
    };

That allows us to use a unique pointer instead:

    auto arry = std::make_unique<T[]>(rows_ * columns_);

And combine the two:

    return [arry = std::make_unique<T[]>(rows_ * columns_), rows_, columns_]
           (size_t row_, size_t col_) -> T&
    {
        // arry is moved into here
        return arry[row_ * columns_ + col_];
    };

The lifetime is correct - the capture is destructed when the lambda is (and Valgrind confirms that, if there was any doubt).


Having suggested improvements to the use of the smart pointer, it's not what I'd prefer: instead of using a pointer to an array, it's clearer to allocate a std::vector. If we don't resize it, it's exactly equivalent to the shared pointer. Like this:

    return [arry = std::vector<T>(rows_ * columns_), rows_, columns_]
           (size_t row_, size_t col_) mutable -> T&
    {
        // arry is moved into here
        return arry[row_ * columns_ + col_];
    };

Minor: false == foo is normally written !foo.

\$\endgroup\$
  • \$\begingroup\$ The code in the lambda declaration area ... I have not seen this elsewhere. Interesting. \$\endgroup\$ – DBJDBJ Feb 28 at 23:26
  • \$\begingroup\$ Allowed size was completely arbitrary, increased to 0xFFFF per matrix side. \$\endgroup\$ – DBJDBJ Feb 28 at 23:27
  • \$\begingroup\$ In some other code (last week) I found std::vector<T> to be measurably slower vs std::unique_ptr<T[]> ... \$\endgroup\$ – DBJDBJ Feb 28 at 23:29
  • \$\begingroup\$ The performance comparison is surprising; I'd be interested to see how you benchmarked. The syntax for lambda capture with initializer was introduced with C++14. \$\endgroup\$ – Toby Speight Mar 1 at 7:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.