templatesgenericsoption-typeeigen3

std::optional with Eigen::ArrayBase


template <typename Derived>
std::optional<Eigen::Array<typename Derived::Scalar, Eigen::Dynamic, Eigen::Dynamic>>
function1(const std::optional<Eigen::ArrayBase<Derived>>& opt1,
     const std::optional<Eigen::ArrayBase<Derived>>& opt2)
{

}

how to write this kind of template with Eigen::ArrayBase so that it can accept two Array or Matrix or nullopt

like:

ArrayXXd a(4, 3);
ArrayXXd b(4, 3);
auto q = function1(a,b);

I know I can do this:

function2(const Eigen::ArrayBase<Derived1>& opt1,
          const Eigen::ArrayBase<Derived2>& opt2)
{
}

Solution

  • I would advise against it but with some helper functions it can be made to work.

    #include <optional>
    #include <functional>
    // using std::cref, std::reference_wrapper
    
    template<class Derived>
    using optional_const_array_ref =
    std::optional<std::reference_wrapper<const Eigen::ArrayBase<Derived>>>;
    
    template<class Derived>
    optional_const_array_ref<Derived>
    make_optional_const_array(const Eigen::ArrayBase<Derived>& arr) noexcept
    { return std::cref(arr); }
    
    template <typename Derived>
    void function1(optional_const_array_ref<Derived> opt1)
    {
        if(opt1)
            std::cout << opt1->get().transpose() << '\n';
    }
    
    int main()
    {
        Eigen::Array3d b(1., 2., 3.);
        function1(make_optional_const_array(b + 1.));
    }
    

    The reasons why this is hard:

    Several caveats:

    1. You cannot use option-> to access the array inside. You have to go the extra step of using reference_wrapper::get()
    2. Under absolutely no circumstances can you extend the lifetime of that optional or its content. This all seems like a recipe for creating dangling pointers
    3. What derived type should an empty optional have? You have to define some type but you can't necessarily use every type in Eigen. For one, you create an extra template instantiation, which will just waste a lot of compiled code size. Also, you might trigger static assertions and other compile time issues, for example if the scalar type is different from the ones you expect. You probably have to define the type explicitly per call site. E.g. function1(optional_const_array<Eigen::Array3d>{})

    Did I already mention that I don't think this is a good idea? Because I don't think this is a good idea.

    Alternative

    There is very little advantage in an optional reference wrapper compared to a plain pointer. We might as well do this:

    template<class Derived>
    const Eigen::ArrayBase<Derived>*
    make_ptr(const Eigen::ArrayBase<Derived>& arr) noexcept
    { return &arr; }
    
    template <typename Derived>
    void function2(const Eigen::ArrayBase<Derived>* opt1)
    {
        if(opt1)
            std::cout << opt1->transpose() << '\n';
    }
    int main()
    {
        Eigen::Array3d b(1., 2., 3.);
        function2(make_ptr(b + 1));
        function2<Eigen::Array3d>(nullptr);
    }
    

    It's just as unsafe as the reference wrapper but it's cheaper and shorter code. The moment you extend the lifetime of that pointer beyond the expression in which make_ptr is called, you have a dangling pointer.