Optional range check based on template parameter

Let's say I have a class that simply performs an addition for any type T. I want to add an additional range check (based on a template parameter of type bool) that will check whether the result of the addition belongs to the given range, otherwise it will throw. One way to do this is to wrap all the basics of a class in a base class and then specialize in the boolean template parameter. Sort of:

// 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();
  }
};

Now the user can write code, for example:

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;
}

Is there a way to do this without using inheritance? I would suggest that using a nested class would be more efficient, but the following code does not compile.

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;
}

Error with error message (in g ++):

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

? , , ? Dummy? /?

+4
4

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;
    }

, - - - , . ... . .

+3

(, - ). :

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. , , .

+1

.

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");
}

You can simplify it with the template template options to get rid of the redundant float parameter. DummyEmptyBaseClassshows it.

+1
source

You can use composition

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;
  }

//...
};
0
source

All Articles