基于模板参数的可选范围检查
optional range check based on template parameter
假设我有一个 class,它只对任何类型 T 执行加法。我想添加一个可选的范围检查(基于 bool 类型的模板参数),它将检查结果是否添加属于给定范围,否则它将抛出。
一种方法是将 class 的所有基础知识包装在基础 class 中,然后专注于布尔模板参数。类似于:
// 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;
}
有没有不使用继承的方法?我想使用嵌套的 class 会更有效率,但是下面的代码
不编译。
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’
有办法解决这个问题吗?如果是这样,它是否比第一个解决方案更有效?嵌套的 class 会增加 Dummy 的任何实例的大小吗?有没有更优雅的design/implementation?
我会在 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;
}
优点是这是一个普通的成员函数 - 您不会卸载到需要传递成员的其他类型。而且你不需要专门......任何东西。太棒了。
编译器非常擅长消除明显无效的代码(例如布尔模板参数产生的代码)。因此,我会选择最直接的解决方案:
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
的实例化生成任何运行时代码,我会 非常 感到惊讶。事实上,我会认为这是一个优化器错误。
你可以使用组合
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;
}
//...
};
我通常尽量不在函数中使用布尔标志来切换行为。
您可以将范围检查作为策略传递,而不是 bool
模板参数,采用 policy-based design 的样式。这些策略不需要通过继承来关联,因为除了使用它们派生的模板参数之外,没有对模板参数类型的限制。只要提供必要的接口,你可以放入任何你喜欢的类型。这样,我就可以定义两个独立的类,没有任何(继承)关系,并将它们都用作模板参数。缺点是 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
显示了这一点。
假设我有一个 class,它只对任何类型 T 执行加法。我想添加一个可选的范围检查(基于 bool 类型的模板参数),它将检查结果是否添加属于给定范围,否则它将抛出。 一种方法是将 class 的所有基础知识包装在基础 class 中,然后专注于布尔模板参数。类似于:
// 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;
}
有没有不使用继承的方法?我想使用嵌套的 class 会更有效率,但是下面的代码 不编译。
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’
有办法解决这个问题吗?如果是这样,它是否比第一个解决方案更有效?嵌套的 class 会增加 Dummy 的任何实例的大小吗?有没有更优雅的design/implementation?
我会在 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;
}
优点是这是一个普通的成员函数 - 您不会卸载到需要传递成员的其他类型。而且你不需要专门......任何东西。太棒了。
编译器非常擅长消除明显无效的代码(例如布尔模板参数产生的代码)。因此,我会选择最直接的解决方案:
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
的实例化生成任何运行时代码,我会 非常 感到惊讶。事实上,我会认为这是一个优化器错误。
你可以使用组合
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;
}
//...
};
我通常尽量不在函数中使用布尔标志来切换行为。
您可以将范围检查作为策略传递,而不是 bool
模板参数,采用 policy-based design 的样式。这些策略不需要通过继承来关联,因为除了使用它们派生的模板参数之外,没有对模板参数类型的限制。只要提供必要的接口,你可以放入任何你喜欢的类型。这样,我就可以定义两个独立的类,没有任何(继承)关系,并将它们都用作模板参数。缺点是 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
显示了这一点。