4

I'm writing a function that has 1 input and 3 outputs like the following:

void ComputeABC(const Eigen::Vector2d& x,
                Eigen::Matrix2d& a,
                Eigen::Matrix2d& b,
                Eigen::Matrix2d& c)

However, I need my output types to be compatible with both Eigen::Matrix2d and Eigen::Map<Eigen::Matrix2d>. Luckily, Eigen provides a Ref type:

void ComputeABC(const Eigen::Vector2d& x,
                Eigen::Ref<Eigen::Matrix2d> a,
                Eigen::Ref<Eigen::Matrix2d> b,
                Eigen::Ref<Eigen::Matrix2d> c)

Now comes the tricky part. a, b, and c are all expensive to compute, but some intermediate values can be shared in the computation, thus saving some compute. Given that these are expensive, I want to optionally compute each of these. I can do this by making each output type of pointer, and pass in NULL to signal that I don't want to compute that specific value.

void ComputeABC(const Eigen::Vector2d& x,
                Eigen::Ref<Eigen::Matrix2d>* optional_a,
                Eigen::Ref<Eigen::Matrix2d>* optional_b,
                Eigen::Ref<Eigen::Matrix2d>* optional_c)

Unfortunately, this is quite ugly, since the user now has to construct a Ref and then pass it in. Trying to pass in an Eigen::Matrix2d* or Eigen::Map<Eigen::Matrix2d>* will result in a compile error.

Does anyone have any suggestions for how to make this function easier to use, given the following criteria?

  • Adding an additional 3 bools to optionally compute values is quite clunky, and the user will still have to construct a dummy Eigen::Ref<Eigen::Matrix2d> for each unwanted output.
  • The calling code has either a Eigen::Matrix2d or Eigen::Map<Eigen::Matrix2d> for each arg that needs to be populated, preferably with zero copies.
  • Avoid using bare double* arrays as, these don't provide any bounds checking on the memory being used.
  • Any subset of a, b, & c can be requested. ( [a], [a, b], [a, b, c], [b], [b, c], [c] ). Thus, overloads don't scale well.
vpradeep
  • 746
  • 1
  • 6
  • 14
  • Rather than passing in 3 output parameters, is it possible to just return a `std::vector`? This would allow you to return any amount or none. This wouldn't work if the user was passing in specific things so they are in and out parameters. I'm not familiar with eigen so I'm not sure if `Matrix2d` is copyable – Tas May 22 '15 at 03:03
  • "*Does anyone have any suggestions for how to improve this API?*" is very unspecific. How could an answer look for you to be accepted? What I can say is: Four parameters is too much. Output parameters are to be avoided. Raw pointers are deprecated ... well, not really but 'oldschool' to say the least. But without knowing how `ComputeABC` or the surrounding classes look, this all is being a bit theoretical. – TobiMcNamobi May 22 '15 at 06:15
  • @TobiMcNamobi - Thanks for the feedback. I updated the question to be a bit more specific about what I'm looking for in an answer. Hopefully that helps! – vpradeep May 22 '15 at 20:02
  • @Tas - Thanks for the suggestion! Though, if I returned `std::vector`, then I still would need to copy these results into the `Eigen::Map` that's in my client code. Ideally, I'd like to avoid these sorts of copies. – vpradeep May 22 '15 at 21:10
  • Could you not provide 4 overloads of ComputeABC to either simply: `ComputeA`, `ComputeAB` and `ComputeABC`, or probably preferably `ComputeABC(in)`, `ComputeABC(in, out1)`, `ComputeABC(in, out1, out2)` and `ComputeABC(in, out1, out2, out3)`? – Tas May 23 '15 at 00:53
  • @Tas - I need to be able to pick any subset of the outputs, so there's a bit of a combinatorial explosion if I expose every combination of params as its own function signature. I've updated the question to specifically address this issue. Thanks! – vpradeep May 23 '15 at 02:24

3 Answers3

0

You could pass Ref<Matrix<double,2,Dynamic> > for the optional arguments and check if cols()==0.

using namespace Eigen;
typedef Matrix<double,2,Dynamic> Matrix2xd;
void ComputeABC(const Vector2d& x,
            Ref<Matrix2xd> a, Ref<Matrix2xd> b, Ref<Matrix2xd> c)
{
   if(a.cols()>0) { /* compute a */ }
   if(b.cols()>0) { /* compute b */ }
   if(c.cols()>0) { /* compute c */ }
}

To call the function with only computing a and c:

Matrix2d a, c;
Vector2d x(1.0, 2.0);
ComputeABC(x, a, Matrix2xd(), c); // Matrix2xd() constructs a temporary 2x0 matrix

If (inside ComputeABC) the expression you assign to a is known to have 2 columns, you should not even experience a difference vs Matrix2d, except for an assertion check that a indeed has two columns.

chtz
  • 17,329
  • 4
  • 26
  • 56
-1

I'm not sure of how you can make a clean interface with so many combinations of output arguments in a single function. I'd instead probably lean towards turning it into a class.

class ABCComputer {
public:
  setInput(const Eigen::Vector2d& x);

  getOptionalA(Eigen::Ref<Eigen::Matrix2d>);
  getOptionalB(Eigen::Ref<Eigen::Matrix2d>);
  getOptionalC(Eigen::Ref<Eigen::Matrix2d>);
};

Because you say the computations of A, B, and C share intermediate values, you can compute the requested values all at once by specifying which outputs are desired with flags:

enum ABCOptions {
  ComputeA = 0x01,
  ComputeB = 0x02,
  ComputeC = 0x04
};


class ABCComputer {
public:
  compute(const Eigen::Vector2d& x, unsigned int options);

  getOptionalA(Eigen::Ref<Eigen::Matrix2d>);
  getOptionalB(Eigen::Ref<Eigen::Matrix2d>);
  getOptionalC(Eigen::Ref<Eigen::Matrix2d>);
};

and call it with something like

ABCComputer abc;
abc.compute(x, ComputeA | ComputeC);
abc.getOptionalA(my_A);
abc.getOptionalC(my_C);
Sean
  • 3,002
  • 1
  • 26
  • 32
-1

You can wrap the Eigen::Ref in a c++17 std::optional. There is a backport to c++11.

Another option uses the fact that EigenRef is default constructible to do something like this:

template<class Obj>
class Opt
{
    const bool has;
    Obj obj;
public:
    Opt(std::nullptr_t n=nullptr) : has(false) {}
    template<class T>
    Opt(T && o) : has(true), obj(std::forward<T>(o)) {}
    Obj const& operator*() { return obj; }
    operator bool() { return has; }
};

using namespace Eigen;

void print(Opt<Ref<VectorXf>> a, Opt<std::string> b)
{
    if(a) { std::cout << "a=" << *a << std::endl; }
    if(b) { std::cout << "b=" << *b << std::endl; }
}

int main()
{
    VectorXf aaa;
    print(aaa, nullptr);
    std::cout << "Hello World!\n";
}
kylefinn
  • 2,398
  • 1
  • 14
  • 12