Search code examples
c++reference-wrapper

Generalising a C++ templated function so it can accept std::reference_wrapper or non-wrapped type of N elements


I have a function that accepts N position vectors and updates them:

using Vector3r = Eigen::Matrix<Real, 3, 1, Eigen::DontAlign>;
template<unsigned int N>
using Vectorr = Eigen::Matrix<Real, N, 1, Eigen::DontAlign>;

template <unsigned int N>
bool solveFbd(const std::reference_wrapper<Vector3r> (&positions)[N]);

The force

I have another function that accepts N position vectors and gives the force

template <unsigned int N>
void forceInternal(const std::reference_wrapper<Vector3r> (&positions)[N], Vectorr<N*3>& forces);

Until now I was simply passing the positions parameter inside solveFbd as an argument to a forceInternal call.

forceInternal(positions);

I copied the type parameter of parameter position for forceInternal from solveFbd blindly, however I don't modify positions in this so it should have ideally been const std::reference_wrapper<const Vector3r> (&positions)[N]. Unfortunately adding this extra const gives me errors with this call. If there is some way to do convert the positions to std::reference_wrapper<const Vector3r> (&positions)[N] it would greatly help but it is not the main problem I am facing here.

Now, I also need to do something like this:

Vector3r positionsCopy[N];
Vectorr<3*N> forces;
for (unsigned int p = 0; p < N; p++)
{
    // I need to make a copy here, I can't change that original `positions`
    positionsCopy[p] = positions[p] + delta[p];
}
forceInternal(positionsCopy, forces);

Unfortunately I get this error when the function solveFbd is instantiated with template parameter 3u:

C:\Users\sin3point14\my-projs\gpbd\PositionBasedDynamics\FBD.cpp(581): error C2664: '_Ret std::_Func_class<_Ret,const std::reference_wrapper<Vector3r> (&)[3],Eigen::Matrix<Real,9,1,2,9,1> &>::operator ()(const std::reference_wrapper<Vector3r> (&)[3],Eigen::Matrix<Real,9,1,2,9,1> &) const': cannot convert argument 1 from 'Vector3r [3]' to 'const std::reference_wrapper<Vector3r> (&)[3]'
        with
        [
            _Ret=void
        ]
C:\Users\sin3point14\my-projs\gpbd\PositionBasedDynamics\FBD.cpp(581): note: Reason: cannot convert from 'Vector3r [3]' to 'const std::reference_wrapper<Vector3r> [3]'
C:\Users\sin3point14\my-projs\gpbd\PositionBasedDynamics\FBD.cpp(581): note: There is no context in which this conversion is possible

From the error I understood that the conversion isn't possible, but I don't want to make a separate overload for forceInternal to accept Vector3r [3]. There are many such forceInternal functions for different force models in my codebase. Is it possible to somehow modify my existing solveFbd or forceInternal to handle my problem.

minimal reproducible example:

#include <Eigen/Dense>

using Real = double;

using Vector3r = Eigen::Matrix<Real, 3, 1, Eigen::DontAlign>;
template<unsigned int N>
using Vectorr = Eigen::Matrix<Real, N, 1, Eigen::DontAlign>;

template <unsigned int N>
void forceInternal(const std::reference_wrapper<Vector3r>(&positions)[N], Vectorr<N * 3>& forces)
{
    // do some stuff
}

template <unsigned int N>
bool solveFbd(const std::reference_wrapper<Vector3r>(&positions)[N])
{
    Vectorr<N * 3> forces;
    forceInternal(positions, forces); // no issues here
    Vector3r positionsCopy[N];
    Vector3r delta[N];
    for (unsigned int p = 0; p < N; p++)
    {
        // I need to make a copy here, I can't change that original `positions`
        positionsCopy[p] = positions[p].get() + delta[p];
    }
    forceInternal(positionsCopy, forces); // error
}

int main()
{
    Vector3r x1, x2, x3;
    solveFbd({ x1, x2, x3 });
}

Error:

Build started...
1>------ Build started: Project: ConsoleApplication4, Configuration: Debug x64 ------
1>ConsoleApplication4.cpp
1>C:\Users\sin3point14\my-projs\ConsoleApplication4\ConsoleApplication4\ConsoleApplication4.cpp(27,5): error C2672: 'forceInternal': no matching overloaded function found
1>C:\Users\sin3point14\my-projs\ConsoleApplication4\ConsoleApplication4\ConsoleApplication4.cpp(10,6): message : could be 'void forceInternal(const std::reference_wrapper<Vector3r> (&)[N],Eigen::Matrix<Real,N*3,1,2,_Rows,1> &)'
1>C:\Users\sin3point14\my-projs\ConsoleApplication4\ConsoleApplication4\ConsoleApplication4.cpp(27,5): message : 'void forceInternal(const std::reference_wrapper<Vector3r> (&)[N],Eigen::Matrix<Real,N*3,1,2,_Rows,1> &)': could not deduce template argument for 'const std::reference_wrapper<Vector3r> (&)[N]' from 'Vector3r [3]'
1>C:\Users\sin3point14\my-projs\ConsoleApplication4\ConsoleApplication4\ConsoleApplication4.cpp(33,2): message : see reference to function template instantiation 'bool solveFbd<3>(const std::reference_wrapper<Vector3r> (&)[3])' being compiled
1>Done building project "ConsoleApplication4.vcxproj" -- FAILED.
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
========== Build started at 5:55 PM and took 01.610 seconds ==========

Solution

  • You can make the function template accept arrays of any type:

    template <typename T, unsigned int N>
    void forceInternal(const T(&positions)[N], Vectorr<N * 3>& forces)
    {
        // do some stuff
    }
    

    If necessary you can add constraints that T must be either td::reference_wrapper<Vector3r> or Vector3r.

    You miss to return a value from solveFbd. After fixing that your code looks ok: https://godbolt.org/z/Ts4TMbfK7