3

I am looking for a convenient to create a C++ class where some member variables are only present if a template flag is set. As a simple example, let's assume I want to toggle an averageSum in an performance sensitive calculation, i.e.

struct Foo {
    // Some data and functions..
    
    void operator+=(const Foo& _other) {}
};



template<bool sumAverages>
class Calculator {
public:
    // Some member variables...
    
    // Those should only be present if sumAverages is true
    int count = 0;
    Foo resultSum;
    
    void calculate(/* some arguments */) {
        // Calculation of result...
        Foo result;
        
        // This should only be calculated if sumAverages is true
        ++count;
        resultSum += result;
        
        // Possibly some post processing...
    }
};

One way would be using preprocessor defines, but those are rather inconvenient especially if I need both versions in the same binary. So I am looking for an alternative using templates and if constexpr and something like the following Conditional class:

template<bool active, class T>
struct Conditional;

template<class T>
struct Conditional<true, T> : public T {};

template<class T>
struct Conditional<false, T> {};

My first shot was this:

template<bool sumAverages>
class Calculator {
public:
    int count = 0;
    Conditional<sumAverages, Foo> resultSum;
    
    void calculate(/* some arguments */) {
        Foo result;
        
        if constexpr(sumAverages) {
            ++count;
            resultSum += result;
        }
    }
};

The if constexpr should incur no run time cost and as it is dependent on a template variable should allow non-compiling code in the false case (e.g. in this example Conditional<false, Foo> does not define a += operator, still it compiles). So this part is more or less perfect. However the variables count and resultSum are still somewhat present. In particular, as one can not derive from a fundamental type, the Conditional class does not allow to toggle the int dependent on the template. Furthermore every Conditional<false, T> variable still occupies one byte possibly bloating small classes. This could be solvable by the new [[no_unique_address]] attribute, however my current compiler chooses to ignore it in all my tests, still using at leas one byte per variable.

To improve things I tried inheriting the variables like this

struct OptionalMembers {
    int count;
    Foo resultSum;
};

template<bool sumAverages>
class Calculator : public Conditional<sumAverages, OptionalMembers> {
public:
    void calculate(/* some arguments */) {
        Foo result;
        
        if constexpr(sumAverages) {
            ++OptionalMembers::count;
            OptionalMembers::resultSum += result;
        }
    }
};

This should come at no space cost as inheriting from am empty class should do literally nothing, right? A possible disadvantage is that one cannot freely set the order of the variables (the inherited variables always come first).

My questions are:

Do you see any problems using the approaches described above?

Are there better options to de(activate) variables like this?

Haatschii
  • 9,021
  • 10
  • 58
  • 95

1 Answers1

6

There are a different ways to solve this, one straightforward one would be using template specialization:

#include <iostream>

template <bool b> struct Calculator {
  int calculate(int i, int j) { return i + j; }
};

template <> struct Calculator<true> {
  int sum;
  int calculate(int i, int j) { return sum = i + j; }
};

int main(int argc, char **argv) {
  Calculator<false> cx;
  cx.calculate(3, 4);
  /* std::cout << cx.sum << '\n'; <- will not compile */

  Calculator<true> cy;
  cy.calculate(3, 4);
  std::cout << cy.sum << '\n';

  return 0;
}

Another solution would be to use mixin-like types to add features to your calculator type:

#include <iostream>
#include <type_traits>

struct SumMixin {
  int sum;
};

template <typename... Mixins> struct Calculator : public Mixins... {
  int calculate(int i, int j) {
    if constexpr (is_deriving_from<SumMixin>()) {
      return SumMixin::sum = i + j;
    } else {
      return i + j;
    }
  }

private:
  template <typename Mixin> static constexpr bool is_deriving_from() {
    return std::disjunction_v<std::is_same<Mixin, Mixins>...>;
  }
};

int main(int argc, char **argv) {
  Calculator<> cx;
  cx.calculate(3, 4);
  /* std::cout << cx.sum << '\n'; <- will not compile */

  Calculator<SumMixin> cy;
  cy.calculate(3, 4);
  std::cout << cy.sum << '\n';

  return 0;
}
Ton van den Heuvel
  • 10,157
  • 6
  • 43
  • 82