Home > Net >  Overload binary operator as friend of templated class: Not recognized as friend or linker error
Overload binary operator as friend of templated class: Not recognized as friend or linker error

Time:10-30

There are quite some posts about this topic already, but neither proposed solution did help me to compile and/or link my code.

The general proposed solution to this is to forward declare the class, forward declare the operator/function, declare it as a friend and then implement it.

The code I try to compile is:

#include "matrix.hpp"

int main()
{
    using namespace MLL;
    Matrix<int, 4, 4> a({1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16});
    a a;
    return 0;
}

Variant 1

#include <algorithm>
#include <array>
#include <type_traits>
#include <vector>

namespace MLL{
    template<typename data_t, std::size_t n_rows, std::size_t n_cols, std::size_t MAX = 256>
    class Matrix;

    template<typename data_t, typename T, std::size_t n_rows, std::size_t n_cols, std::size_t MAX, std::size_t other_MAX>
    Matrix<decltype(std::declval<data_t>()   std::declval<T>()), n_rows, n_cols, std::min(MAX, other_MAX)>
    operator (Matrix<data_t, n_rows, n_cols, MAX> const& lhs, Matrix<T, n_rows, n_cols, other_MAX> const& rhs);

    template<typename data_t, std::size_t n_rows, std::size_t n_cols, std::size_t MAX>
    class Matrix{
        static constexpr bool IS_STATIC = n_rows * n_cols <= MAX;
        using container_t = typename std::conditional<IS_STATIC, std::array<data_t, n_rows * n_cols>, std::vector<data_t>>::type;

        container_t m_data_list;

    public:
        Matrix(){
            if constexpr( !IS_STATIC ){
                m_data_list.resize(n_rows * n_cols);
            }
        }

        explicit Matrix(data_t default_value){
            if constexpr( IS_STATIC ){
                m_data_list.fill(default_value);
            }else{
                m_data_list.resize(n_rows * n_cols, default_value);
            }
        }

        explicit Matrix(std::initializer_list<data_t>&& value_list){
            std::copy(value_list.begin(), value_list.end(), m_data_list.begin());
        }

        Matrix(Matrix const& other)
        : m_data_list(other.m_data_list){
        }

        Matrix(Matrix&& other) noexcept
        : m_data_list(std::move(other.m_data_list)){
        }

        Matrix& operator=(Matrix const& other){
            m_data_list = other.m_data_list;
            return *this;
        }

        Matrix& operator=(Matrix&& other) noexcept{
            m_data_list = std::move(other.m_data_list);
            return *this;
        }

        template<typename T, std::size_t other_MAX>
        friend Matrix<decltype(std::declval<data_t>()   std::declval<T>()), n_rows, n_cols, std::min(MAX, other_MAX)>
        operator (Matrix<data_t, n_rows, n_cols, MAX> const& lhs, Matrix<T, n_rows, n_cols, other_MAX> const& rhs);
    };

    template<typename data_t, typename T, std::size_t n_rows, std::size_t n_cols, std::size_t MAX, std::size_t other_MAX>
    Matrix<decltype(std::declval<data_t>()   std::declval<T>()), n_rows, n_cols, std::min(MAX, other_MAX)>
    operator (Matrix<data_t, n_rows, n_cols, MAX> const& lhs, Matrix<T, n_rows, n_cols, other_MAX> const& rhs){
        const std::size_t n = n_rows * n_cols;
        for( std::size_t i = 0; i < n;   i ){
            lhs.m_data_list[i]  = rhs.m_data_list[i];
        }
        return lhs;
    }
}

Here the linker complains that the function is not implemented.

undefined reference to `MLL::Matrix<decltype (((std::declval<int>)()) ((declval<int>)())), 4ull, 4ull, (std::min<unsigned long long>)(256ull, 256ull)> MLL::operator <int, 256ull>(MLL::Matrix<int, 4ull, 4ull, 256ull> const&, MLL::Matrix<int, 4ull, 4ull, 256ull> const&)'

Variant 2

#include <algorithm>
#include <array>
#include <type_traits>
#include <vector>

namespace MLL{
    template<typename data_t, std::size_t n_rows, std::size_t n_cols, std::size_t MAX = 256>
    class Matrix;

    template<typename data_t, typename T, std::size_t n_rows, std::size_t n_cols, std::size_t MAX, std::size_t other_MAX>
    Matrix<decltype(std::declval<data_t>()   std::declval<T>()), n_rows, n_cols, std::min(MAX, other_MAX)>
    operator (Matrix<data_t, n_rows, n_cols, MAX> const& lhs, Matrix<T, n_rows, n_cols, other_MAX> const& rhs);

    template<typename data_t, std::size_t n_rows, std::size_t n_cols, std::size_t MAX>
    class Matrix{
        static constexpr bool IS_STATIC = n_rows * n_cols <= MAX;
        using container_t = typename std::conditional<IS_STATIC, std::array<data_t, n_rows * n_cols>, std::vector<data_t>>::type;

        container_t m_data_list;

    public:
        Matrix(){
            if constexpr( !IS_STATIC ){
                m_data_list.resize(n_rows * n_cols);
            }
        }

        explicit Matrix(data_t default_value){
            if constexpr( IS_STATIC ){
                m_data_list.fill(default_value);
            }else{
                m_data_list.resize(n_rows * n_cols, default_value);
            }
        }

        explicit Matrix(std::initializer_list<data_t>&& value_list){
            std::copy(value_list.begin(), value_list.end(), m_data_list.begin());
        }

        Matrix(Matrix const& other)
                : m_data_list(other.m_data_list){
        }

        Matrix(Matrix&& other) noexcept
                : m_data_list(std::move(other.m_data_list)){
        }

        Matrix& operator=(Matrix const& other){
            m_data_list = other.m_data_list;
            return *this;
        }

        Matrix& operator=(Matrix&& other) noexcept{
            m_data_list = std::move(other.m_data_list);
            return *this;
        }

        template<typename T, typename U, std::size_t m_rows, std::size_t m_cols, std::size_t this_MAX, std::size_t other_MAX>
        friend Matrix<decltype(std::declval<T>()   std::declval<U>()), m_rows, m_cols, std::min(this_MAX, other_MAX)>
        operator (Matrix<T, m_rows, m_cols, this_MAX> const& lhs, Matrix<T, m_rows, m_cols, other_MAX> const& rhs);
    };

    template<typename data_t, typename T, std::size_t n_rows, std::size_t n_cols, std::size_t MAX, std::size_t other_MAX>
    Matrix<decltype(std::declval<data_t>()   std::declval<T>()), n_rows, n_cols, std::min(MAX, other_MAX)>
    operator (Matrix<data_t, n_rows, n_cols, MAX> const& lhs, Matrix<T, n_rows, n_cols, other_MAX> const& rhs){
        const std::size_t n = n_rows * n_cols;
        for( std::size_t i = 0; i < n;   i ){
            lhs.m_data_list[i]  = rhs.m_data_list[i];
        }
        return lhs;
    }
}

Error 2

C:/Users/CLionProjects/MLL/include/matrix.hpp:68:17: error: 'MLL::Matrix<int, 4, 4>::container_t MLL::Matrix<int, 4, 4>::m_data_list' is private within this context
   68 |             lhs.m_data_list[i]  = rhs.m_data_list[i];
      |             ~~~~^~~~~~~~~~~
C:/Users/CLionProjects/MLL/include/matrix.hpp:19:21: note: declared private here
   19 |         container_t m_data_list;
      |                     ^~~~~~~~~~~
C:/Users/CLionProjects/MLL/include/matrix.hpp:68:39: error: 'MLL::Matrix<int, 4, 4>::container_t MLL::Matrix<int, 4, 4>::m_data_list' is private within this context
   68 |             lhs.m_data_list[i]  = rhs.m_data_list[i];
      |                                   ~~~~^~~~~~~~~~~
C:/Users/CLionProjects/MLL/include/matrix.hpp:19:21: note: declared private here
   19 |         container_t m_data_list;
      |                     ^~~~~~~~~~~h
C:/Users/CLionProjects/MLL/include/matrix.hpp:68:32: error: assignment of read-only location 'lhs.MLL::Matrix<int, 4, 4>::m_data_list.std::array<int, 16>::operator[](i)'
   68 |             lhs.m_data_list[i]  = rhs.m_data_list[i];
      |             ~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~
ninja: build stopped: subcommand failed.

Question

I do not want to define it within the friend-declaration.

Can you please explain what I'm doing wrong in both cases, and how I can fix it?

CodePudding user response:

The problem with the first snippet is that the friend declaration for the function template operator has only two parameters while the implemented operator has 6 template parameters. That is, the friend declaration is for a different function template than the one you implemented.

Additionally note that you don't actually need the forward declarations for both the Matrix template as well as the overloaded operator .

In the below modified program, I've removed those forward declarations(but you can have them if you want) and I've highlighted using comments the changes that I've made. In case you do decide to have the forward declaration, make sure that you only provide the default argument 256 only in the forward declaration and not in the definition.

Additionally, for better readibility I've added postfixed OP after each template parameter of the overloaded operator so that it can easily be separated from template parameters of the enclosing class template.

Moreover, as lhs is a const lvalue reference we can't do the assignment lhs.m_data_list[i] = rhs.m_data_list[i] using that reference. So in the code below, I've commented out that statement as shown by my comment.

Working demo

namespace MLL{
//   template<typename data_t, std::size_t n_rows, std::size_t n_cols, std::size_t MAX=256>
//     class Matrix;    //this forward declaration not needed but you can have this if you want and if you do then make sure that you only provide the default arg 256 in declaration and not in definition
//--------------------------------------------------------------------------------vvvvvvv---->added this default arg here instead of in forward declaration
    template<typename data_t, std::size_t n_rows, std::size_t n_cols, std::size_t MAX=256>
    class Matrix{
        static constexpr bool IS_STATIC = n_rows * n_cols <= MAX;
        using container_t = typename std::conditional<IS_STATIC, std::array<data_t, n_rows * n_cols>, std::vector<data_t>>::type;

        container_t m_data_list;

    public:
        Matrix(){
            if constexpr( !IS_STATIC ){
                m_data_list.resize(n_rows * n_cols);
            }
        }

        explicit Matrix(data_t default_value){
            if constexpr( IS_STATIC ){
                m_data_list.fill(default_value);
            }else{
                m_data_list.resize(n_rows * n_cols, default_value);
            }
        }

        explicit Matrix(std::initializer_list<data_t>&& value_list){
            std::copy(value_list.begin(), value_list.end(), m_data_list.begin());
        }

        Matrix(Matrix const& other)
                : m_data_list(other.m_data_list){
        }

        Matrix(Matrix&& other) noexcept
                : m_data_list(std::move(other.m_data_list)){
        }

        Matrix& operator=(Matrix const& other){
            m_data_list = other.m_data_list;
            return *this;
        }

        Matrix& operator=(Matrix&& other) noexcept{
            m_data_list = std::move(other.m_data_list);
            return *this;
        }

        //renamed all the arguments by prefexing them with OP for better readibility
        template<typename data_tOP, typename TOP, std::size_t n_rowst, std::size_t n_colsOP, std::size_t MAXOP, std::size_t other_MAXOP>
    friend Matrix<decltype(std::declval<data_tOP>()   std::declval<TOP>()), n_rowst, n_colsOP, std::min(MAXOP, other_MAXOP)>
    operator (Matrix<data_tOP, n_rowst, n_colsOP, MAXOP> const& lhs, Matrix<TOP, n_rowst, n_colsOP, other_MAXOP> const& rhs);
    };

     template<typename data_tOP, typename TOP, std::size_t n_rowst, std::size_t n_colsOP, std::size_t MAXOP, std::size_t other_MAXOP>
    Matrix<decltype(std::declval<data_tOP>()   std::declval<TOP>()), n_rowst, n_colsOP, std::min(MAXOP, other_MAXOP)>
    operator (Matrix<data_tOP, n_rowst, n_colsOP, MAXOP> const& lhs, Matrix<TOP, n_rowst, n_colsOP, other_MAXOP> const& rhs){
         const std::size_t n = n_rowst * n_colsOP;
        for( std::size_t i = 0; i < n;   i ){
           // lhs.m_data_list[i]  = rhs.m_data_list[i];   //can't assing using const lvalue reference
        }
        return lhs;
    }
}

Working demo

  • Related