Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

optional range check based on template parameter

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

// 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 like:

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 of doing this without using inheritance? I would suppose 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;
}

It fails with an error message (in g++):

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

Is there a way around this? If so, is it more efficient than the first solution? Will the nested class add size to any instance of Dummy? Is there a more elegant design/implementation?

like image 736
xnth Avatar asked Sep 02 '15 11:09

xnth


People also ask

What is the difference between a function template and a parameter pack?

In a function template, there are no restrictions on the parameters that follow a default, and a parameter pack may be followed by more type parameters only if they have defaults or can be deduced from the function arguments.

How do I parameterize a template?

Every template is parameterized by one or more template parameters, indicated in the parameter-list of the template declaration syntax: template < parameter-list > declaration Each parameter in parameter-list may be: a non-type template parameter;

What are the different types of parameters in parameter-list?

Each parameter in parameter-list may be: a template template parameter. 1) A non-type template parameter with an optional name. 2) A non-type template parameter with an optional name and a default value. 3) A non-type template parameter pack with an optional name.

What is a type parameter in a template?

Type template parameter. In the body of the template declaration, the name of a type parameter is a typedef-name which aliases the type supplied when the template is instantiated. There is no difference between the keywords class and typename in a type template parameter declaration.


2 Answers

I would just dispatch on RC. And making it a type:

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

With that:

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

The advantage there is that this is a normal member function - you're not offloading onto some other type that you need to pass members around to. And you don't need to specialize... anything. Which is great.

like image 186
Barry Avatar answered Nov 15 '22 01:11

Barry


I usually try to not use boolean flags in functions to switch behavior.

You can pass the range-check as a policy instead of the bool template parameter, in the style of policy-based design. The policies do not need a be related by inheritance, because there are no constraints on the type of the template arguments except the ones derived from using them. You can put in any type you like as long as it provides the necessary interface. This way, I can define two independent classes without any (inheritance) relationship, and use both of them as template parameters. The drawback is that Dummy<float, X> and Dummy<float, Y> are two different, unrelated types and you cannot e.g. assign an instance of the first type to an instance of the second one without defining a template assignment operator.

#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 more with template-template parameters to get rid of the redundant float parameter. DummyEmptyBaseClass shows this.

like image 43
Jens Avatar answered Nov 15 '22 01:11

Jens