0

Context

I'm writing a math lib for training. Right now I'm working on the Matrix class. I'm trying to make a identityMatrix() function. This is a templated function that builds and returns a identity matrix. Right now my code is:

template<index_t N, typename Scalar = double>
constexpr inline static const Matrix<N, N, Scalar> identityMatrix() noexcept
{
    constexpr static Matrix<N, N, Scalar> id = []
    {
        Matrix<N, N, Scalar> m(0);
        for (index_t i = 0; i < N; i++)
        {
            m.at(i, i) = static_cast<Scalar>(1.0);
        }
        return m;
    }();
    return id;
}

I'm trying to make it so the matrix is build once only if needed, any later reference to the function returns a function local static const object. As the initialization is complex I'm using a lambda and calling it right away. The Matrix definition is as following:

using index_t = uint32_t;
#define FOR(i, N) for(index_t i = 0; i < N; ++i)
#define FORiN(N) FOR(i, N)
template<index_t ROWS, index_t COLS = ROWS, typename Scalar = double>
struct Matrix 
{
    static constexpr auto BUFFER_SIZE = ROWS * COLS;
    Scalar buffer[BUFFER_SIZE];
    //...
    constexpr Matrix() noexcept = default;

    constexpr Matrix(const Scalar (&init_data)[BUFFER_SIZE]) noexcept
    {
        memcpy_s(buffer, BUFFER_SIZE * sizeof Scalar, &init_data , BUFFER_SIZE * sizeof Scalar);
    }
    constexpr Matrix(const std::initializer_list<std::initializer_list<Scalar>>& init_data) 
    {
        static_assert(init_data.size() == ROWS && init_data.begin()->size() == COLS);
        FOR(row_index, ROWS) 
        {
            Scalar* src_row = &init_data.begin()[row_index];
            Scalar* dst_row = &buffer[row_index * COLS];
            constexpr index_t row_size_bytes = COLS * sizeof Scalar;
            memcpy_s(dst_row, row_size_bytes, src_row, row_size_bytes);
        }
    }
    constexpr Matrix(Scalar homogenuos_value) noexcept
    {
        std::fill_n(buffer, BUFFER_SIZE, homogenuos_value);
    }
    constexpr Matrix(const Matrix& rhs) noexcept 
    {
        FOR(i, BUFFER_SIZE)
            buffer[i] = rhs.buffer[i];
    }
    inline constexpr static index_t indexOf(index_t col, index_t row) { return row * COLS + col; }

    inline constexpr Scalar& at(index_t row, index_t col) { return buffer[indexOf(row, col)]; }
    inline constexpr const Scalar& at(index_t row, index_t col) const { return buffer[indexOf(row, col)]; }
    //... more operators and stuff
};

The problem

The test code i'm writting to test my matrix code is as following:

    constexpr auto id = identityMatrix<3>();
    constexpr auto manual_id = Matrix<3, 3>({ 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0 });
    static_assert(id == manual_id);
    print(id);

Visual Studio gives me this: And also:

Why

Why is this not working? I have make everything constexpr this should be constant at compile time. The compiler should be able to make this. What am I missing? What am I doing wrong?

Enviroment

I'm using

Microsoft Visual Studio Professional 2019 - version 16.4.2
Platform toolset: Visual Studio 2019 (v142)
C++ Language Standard: Preview - Features from the Latest C++ Working Draft (std:c++latest)
Today's date: 9 june 2020

2 Answers 2

0

This is because the function memcpy_s is not a constexpr function.

When the compiler parse templated constexpr function, it does not check whether those functions are constexpr. Rather, it checks constexprness at the instantiation of those function. If theses instances are not constexpr, this is not an error.

This is what happens here, none of the Matrix<3,3> constructor are constexpr because memcpy_s is not constexpr.

Sign up to request clarification or add additional context in comments.

4 Comments

Does it really ? I think that's the case only if that template instantiation is actually attempted to be used in a constexpr context, not at the time of instantiation itself.
@PiotrSkotnicki At the time of instantiation could it be a "no diagnostic required" if no call to this function can be constexpr?
But isn't it the next bullet that applies here (7), i.e., "or member function of a class template [...]" ?
@PiotrSkotnicki Whatsoever the function is not required to be constexpr, why did we ask ourselve those questions?
0

Why is this not working? I have make everything constexpr this should be constant at compile time. The compiler should be able to make this. What am I missing? What am I doing wrong?

Without a complete example (what is index_t? what is FOR()? etc.) it's difficult to say exactly but...

(1) a constexpr constructor must initialize non-static data members. I mean: in initialization list.

So

constexpr Matrix(Scalar homogenuos_value) noexcept

is wrong because you have to initialize buffer

// ..................................................vvvvvvvv
constexpr Matrix(Scalar homogenuos_value) noexcept : buffer{}

This also for other constructors

(2) a constexpr function can't call (or can't executed compile-time) a non constexpr function.

But you call std::fill_n() (with three arguments) in one of your constexpr constructor.

That std::fill_n() is constexpr only starting from C++20.

Are you compiling C++20 ?

And memcpy_s() (that I don't know because is Microsoft specific and I use Linux) is constexpr?

(3) as far I know, a static variable isn't permitted inside a constexpr function, so your id variable

 constexpr static Matrix<N, N, Scalar> id =

inside the constexpr identityMatrix() function can't work

To solve this problem I propose the use of a static constexpr template variable iMatrix instead of identityMatrix() function

template<index_t N, typename Scalar = double>
static constexpr auto iMatrix =  []
    {
        Matrix<N, N, Scalar> m(0);
        for (index_t i = 0; i < N; i++)
        {
            m.at(i, i) = static_cast<Scalar>(1.0);
        }
        return m;
    }();

// ...

constexpr auto id = iMatrix<3>;

3 Comments

Sorry for the FOR() and index_t thing, I though It was implicit enough; I've edited the question with it's definition. (1) okay, I undertand, will try. (2) I though I was, but now i'm seeing lastest may not be c++20, this installations has a bunch months old (you know corporate stuff) (3) then I cannot use the single initialization of static variables? how can I pre-build a identity matrix once per size (the template sizes)? you know any way? Should then use simply a for to copy the bufffer and rely on compiler optimizations?
@BetaCoder - about (3)... what about a static constexpr template variable? Is available starting from C++14.
@BetaCoder - answer improved adding, for (3), an example for a static constexpr template variable.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.