模板(去)激活的成员变量

Haa*_*hii 3 c++ templates constexpr c++20

我正在寻找一种方便的方法来创建一个C++类,其中某些成员变量仅在设置了模板标志时才存在。作为一个简单的例子,我们假设我想在性能敏感的计算中切换averageSum,即

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...
    }
};
Run Code Online (Sandbox Code Playgroud)

一种方法是使用预处理器定义,但这些相当不方便,特别是如果我需要同一二进制文件中的两个版本。所以我正在寻找使用模板和if constexpr类似以下Conditional类的替代方案:

template<bool active, class T>
struct Conditional;

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

template<class T>
struct Conditional<false, T> {};
Run Code Online (Sandbox Code Playgroud)

我的第一枪是这样的:

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;
        }
    }
};
Run Code Online (Sandbox Code Playgroud)

应该if constexpr不会产生运行时成本,并且因为它依赖于模板变量,所以应该允许在false这种情况下使用非编译代码(例如,在本例中Conditional<false, Foo>没有定义+=运算符,但它仍然可以编译)。所以这部分或多或少是比较完美的。然而,变数count仍然resultSum存在。特别是,由于无法从基本类型派生,因此该类Conditional不允许切换int对模板的依赖。此外,每个Conditional<false, T>变量仍然占用一个字节,可能会使小类膨胀。这可以通过新[[no_unique_address]]属性来解决,但是我当前的编译器选择在所有测试中忽略它,每个变量仍然至少使用一个字节。

为了改进我尝试继承这样的变量

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;
        }
    }
};
Run Code Online (Sandbox Code Playgroud)

这应该没有空间成本,因为从空类继承实际上什么也不做,对吗?一个可能的缺点是不能自由设置变量的顺序(继承的变量总是排在第一位)。

我的问题是:

使用上述方法您是否发现任何问题?

是否有更好的选择来取消(激活)这样的变量?

Ton*_*vel 6

有多种方法可以解决这个问题,一种简单的方法是使用模板专门化:

#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;
}
Run Code Online (Sandbox Code Playgroud)

另一个解决方案是使用类似 mixin 的类型来向计算器类型添加功能:

#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;
}
Run Code Online (Sandbox Code Playgroud)