基于模板参数

时间:2015-09-02 11:17:51

标签: c++ templates c++11 nested-class partial-specialization

让我说我有一个只为任何类型T执行添加的类。我想添加一个可选的范围检查(基于bool类型的模板参数),它将检查添加的结果属于给定范围,否则它将抛出。 这样做的一种方法是将类的所有基础包装在基类中,然后专门研究布尔模板参数。类似的东西:

// The base class; holds a starting value to add to and a maximum value
template<typename T>
class DummyImpl
{
private:
  T mval, mmax;

public:
  constexpr explicit DummyImpl(T x, T max_x) noexcept
 : mval{x}, mmax{max_x}
  {};

  // base class; use a virtual destructor
  virtual ~DummyImpl() {};

  T max() const noexcept {return mmax;}
  T val() const noexcept {return mval;}
};

// The "real" class; parameter B denotes if we want (or not)
// a range check
template<typename T, bool B>
class Dummy : DummyImpl<T> {};

// Specialize: we do want range check; if sum not in range
// throw.
template<typename T>
class Dummy<T, true> : DummyImpl<T>
{
public:
  explicit Dummy(T x, T max_x) noexcept : DummyImpl<T>(x, max_x) {};

  T add(T x) const noexcept( !true )
  {
    T ret_val = x + DummyImpl<T>::val();
    if (ret_val < 0 || ret_val > DummyImpl<T>::max()) {
      throw 1;
    }
    return ret_val;
  }
};

// Specialize for no range check.
template<typename T>
class Dummy<T, false> : DummyImpl<T>
{
public:
  explicit Dummy(T x, T max_x) noexcept : DummyImpl<T>(x, max_x) {};

  T add(T x) const noexcept( !false )
  {
    return x + DummyImpl<T>::val();
  }
};

现在用户可以编写如下代码:

int main()
{
  Dummy<float,false> d(0, 1000); //no range check; never throw

  std::cout <<"\nAdding  156.7 gives " << d.add(156.7);
  std::cout <<"\nAdding 3156.7 gives " << d.add(3156.7);

  std::cout <<"\n";
  return 0;
}

有没有办法在不使用继承的情况下执行此操作?我认为使用嵌套类会更有效,但是下面的代码 不编译。

template<typename T,  bool RC>
class Dummy
{
private:
  T mval, mmax;

  // parameter S is only used to enable partial specialization on
  // parameter I
  template<bool I, typename S> struct add_impl {};

  template<typename S> struct add_impl<true, S>
  {
    T operator()(T x) const noexcept( !true )
    {
      T ret_val = x + mval;
      if (ret_val < 0 || ret_val > mmax) {throw 1;}
      return ret_val;
    }
  };

  template<typename S> struct add_impl<false,  S>
  {
    T operator()(T x) const noexcept( !false )
    {
      return x + mval_ref;
    }
  };

public:
  constexpr explicit Dummy(T x, T max_x) noexcept
 : mval{x}, mmax{max_x}
  {};

  void bar() const { std::cout << "\nin Base."; }
  T max() const noexcept {return mmax;}
  T val() const noexcept {return mval;}
  T add(T x) const noexcept( !RC )
  {
    return add_impl<RC, T>()(x);
  }
};


int main()
{
  Dummy<float,false> d(0, 1000);

  std::cout <<"\nAdding  156.7 gives " << d.add(156.7);
  std::cout <<"\nAdding 3156.7 gives " << d.add(3156.7);

  std::cout <<"\n";
  return 0;
}

失败并显示错误消息(以g ++为单位):

error: invalid use of non-static data member ‘Dummy<float, false>::mval’

有解决方法吗?如果是这样,它比第一个解决方案更有效吗?嵌套类是否会为Dummy的任何实例添加大小?是否有更优雅的设计/实施?

4 个答案:

答案 0 :(得分:3)

我只会发送RC。并使其成为一种类型:

template<typename T,  bool RC>
class Dummy
{
private:
    using do_range_check = std::integral_constant<bool, RC>;
    T mval, mmax;
};

有了这个:

    T add(T x) const {
        return add(x, do_range_check{});
    }

private:    
    T add(T x, std::false_type /* range_check */) {
        return x + mval;
    }

    T add(T x, std::true_type /* range_check */) {
        T ret_val = x + mval;
        if (ret_val < 0 || ret_val > mmax) {throw 1;}
        return ret_val;
    }

这样做的好处是这是一个普通的成员函数 - 你不会卸载到你需要传递成员的其他类型。而且你不需要专门化......任何东西。哪个好。

答案 1 :(得分:1)

编译器非常擅长消除明显死代码(例如布尔模板参数引起的代码)。因此,我采用最直接的解决方案:

template<typename T,  bool RC>
class Dummy
{
private:
  T mval, mmax;

public:
  T add(T x) const noexcept( !RC )
  {
    T ret_val = x + val();
    if (RC && (ret_val < 0 || ret_val > DummyImpl<T>::max())) {
      throw 1;
    }
    return ret_val;
  }

//...
};

如果为RC == false的实例化生成了任何运行时代码,我将非常感到惊讶。事实上,我会认为这是一个优化错误。

答案 2 :(得分:1)

我通常尝试不在函数中使用布尔标志来切换行为。

您可以使用policy-based design的样式将范围检查作为策略而不是bool模板参数传递。策略不需要通过继承关联,因为除了使用它们派生的模板参数之外,模板参数的类型没有约束。只要提供必要的界面,您就可以输入任何您喜欢的类型。这样,我可以定义两个没有任何(继承)关系的独立类,并将它们用作模板参数。缺点是Dummy<float, X>Dummy<float, Y>是两种不同的,不相关的类型,你不能例如将第一种类型的实例分配给第二种类型的实例,而不定义模板赋值运算符。

#include <stdexcept>

template<typename T>
struct NoMaxCheck
{
    NoMaxCheck(T) {}

    void check(T) const noexcept {}
};

template<typename T>
struct ThresholdChecker
{
    ThresholdChecker(T value) : mMax(value) {}

    void check(T value) const
    {
          if (value < 0 || mMax < value) {
              throw std::out_of_range("");
          }
    }
private:
    T mMax;
};

template<typename T,  typename CheckPolicy>
class Dummy
{
private:
  T mVal;
  CheckPolicy mThresholdChecker;

public:
  explicit Dummy(T x, T max_x) noexcept : mVal(x), mThresholdChecker(max_x) {};

  T add(T x) const noexcept(noexcept(mThresholdChecker.check(x)))
  {
    T ret_val = x + mVal();
    mThresholdChecker.check(ret_val);
    return ret_val;
  }
};

template<typename T,  template<typename> typename CheckPolicy>
class DummyEmptyBaseClasss: private CheckPolicy<T>
{
private:
  T mVal;

public:
  explicit DummyEmptyBaseClasss(T x, T max_x) noexcept:
      CheckPolicy<T>(max_x),
      mVal(x) {};

  T add(T x) const noexcept(noexcept(check(x)))
  {
    T ret_val = x + mVal();
    check(ret_val);
    return ret_val;
  }
};

int foo()
{
  Dummy<float,NoMaxCheck<float>> unchecked(0, 1000);
  Dummy<float,ThresholdChecker<float>> checked(0, 1000);
  static_assert( sizeof(DummyEmptyBaseClasss<float, NoMaxCheck>) == sizeof(float), "empty base class optimization");
}

您可以使用模板模板参数进行更多简化,以消除冗余浮点参数。 DummyEmptyBaseClass显示了这一点。

答案 3 :(得分:0)

您可以使用合成

template<typename T, bool B> struct ThresholdChecker;

template<typename T>
struct ThresholdChecker<T, true>
{
    ThresholdChecker(T value) : mMax(value) {}

    void check(T value) const
    {
          if (value < 0 || mMax < value) {
              throw std::out_of_range("");
          }
    }
private:
    T mMax;
};

template<typename T>
struct ThresholdChecker<T, false>
{
    ThresholdChecker(T) {}

    void check(T) const noexcept {}
};


template<typename T,  bool RC>
class Dummy
{
private:
  T mval;
  ThresholdChecker<T, RC> mThresholdChecker;

public:
  explicit Dummy(T x, T max_x) noexcept : mVal(x), mThresholdChecker(max_x) {};

  T add(T x) const noexcept(noexcept(mThresholdChecker.check(x)))
  {
    T ret_val = x + val();
    mThresholdChecker.check(ret_val);
    return ret_val;
  }

//...
};