Search code examples
c++c++11typesinitializationarmadillo

C++11 conditional data type


I wrote a C++11 function that conditionally initializes an object. Consider the following example:

arma::mat some_function(bool a, bool b, unsigned long int c) {
  if(a) {
    if(b) {
      arma::sp_mat d(c, c);
    } else {
      arma::SpMat<short> d(c, c);
    }
  } else {
    if(b) {
      arma::mat d(c, c, fill::zeros);
    } else {
      arma::Mat<short> d(c, c, fill::zeros);
    }
  }
  // Some other computations and a return statement
}

As you can see, I want to initialize an object d where the (Armadillo) data type depends on booleans a and b. Unfortunately, it does not compile, with the compiler complaining about d not being declared in the scope where it is subsequently used.

I found various discussions on similar issues. Conditional initialization is apparently introduced in C++17. However, I need to use C++11 in this application. Other suggestions mention the type_traits library and lambda functions. And many discussions focus on conditionally specifying the object's content rather than its data type. As I am still fairly new to C++, I am not sure how to apply any of these options to the this nested conditional structure.


Solution

  • template<class F>
    arma::mat some_function(bool a, bool b, unsigned long int c, F f) {
      if(a) {
        if(b) {
          arma::sp_mat d(c, c);
          return f(d);
        }
        arma::SpMat<short> d(c, c);
        return f(d);
      }
      if(b) {
        arma::mat d(c, c, fill::zeros);
        return f(d);
      }
      arma::Mat<short> d(c, c, fill::zeros);
      return f(d);
    }
    
    arma::mat some_function(bool a, bool b, unsigned long int c){
      return some_function(a, b, c,
        [](auto&& d)->arma::mat{
          // Some other computations and a return statement
        }
      );
    }
    

    I believe this solves your problem, but requires .

    The style here is called "continuation passing style"; the first template creates the variable d, then passes that to a function you passed to it. It then returns what the function it takes returns when it in turn takes a d.

    The code within the lambda at the bottom must be able to handle d being any of the types it could be within the template function, because it doesn't know what the bool values will be.

    (If the bool values are known there are other approaches, but often the answer is "if you know that, and only some are valid, why do the bools exist?)

    The feature was so simple to write most compilers support it if you tell it to use (their early pre-standard version of C++14). If not, you can just replace this:

        [](auto&& d)->arma::mat{
          // Some other computations and a return statement
        }
    

    with

    struct some_helper {
      template<class M>
      arma::mat operator()(M&& m)const {
        // Some other computations and a return statement
      }
    };
    

    outside of some_function, then

    arma::mat some_function(bool a, bool b, unsigned long int c){
      return some_function(a, b, c,
        some_helper{}
      );
    }
    

    In the case, if you need access to a/b/c, change [] to [&].

    In the case, if you need access to a/b/c, add them to some_helper as member variables (or references) and initialize them in some_helper's constructor, and pass them in from some_function.

    The feature I'm using is the terse syntax templated lambda, which is syntactic sugar to generate the trivial class. The lambda has a few more (zero cost) features we don't care about.