5

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?

xnth
  • 156
  • 1
  • 8
  • How you can modify your template code depending on parms: Look for SFINAE or template specialization. But is this really helpful in your use case? Having a bool parameter false which says nothing what false will mean makes code unreadable. – Klaus Sep 02 '15 at 11:27

4 Answers4

4

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.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • Thanks mate! That is indeed clear and elegant. I guess that for the `noexcept` specifications i can use something like `noexcept(do_range_check::value)`. If i get it correctly, `do_range_check{}` yields either an `std::false_type` or an `std::true_type` (depending on `RC`) and not just a boolean variable (as does `do_range_check::value`) right? – xnth Sep 02 '15 at 13:06
  • @xnth `noexcept(!do_range_check::value)`, otherwise all correct. – Barry Sep 02 '15 at 13:59
1

Compilers are quite good at elimination of obviously dead code (such as that arising from boolean template parameters). I'd therefore go with the most straightforward solution:

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

//...
};

I would be extremely surprised if any runtime code were generated for the instantiation where RC == false. In fact, I would consider that an optimiser bug.

roalz
  • 2,699
  • 3
  • 25
  • 42
Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • @Agnew thanks mate; that was my initial design but i was not sure whether the compiler would optimize this away. Seems like a trivial case after all. – xnth Sep 02 '15 at 12:39
1

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.

Jens
  • 9,058
  • 2
  • 26
  • 43
  • The code won't compile. I don't understand the `NoMaxCheck` struct. Is it supposed to be derived from `ThresholdChecker`? If the `CheckPolicy` is `NoMaxCheck`, would the instance of a `Dummy` object get larger than needed in size, or would Empty Base Optimization kick in? – xnth Sep 02 '15 at 13:42
  • @xnth If fixed the example code and expanded the explanation. If you change `Dummy` to privately inherit from `CheckPolicy`, empty base-class optimization should remove the space overhead. – Jens Sep 02 '15 at 14:49
  • @xnth I've added an example with empty base class optimization. – Jens Sep 02 '15 at 15:08
0

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

//...
};
Jarod42
  • 203,559
  • 14
  • 181
  • 302