【发布时间】:2021-12-30 02:05:08
【问题描述】:
我愿意实现惰性来避免一些昂贵的计算,并将计算延迟到需要其结果的点。为了以透明的方式做到这一点,我想到了将计算包装在惰性类的转换运算符中:惰性函数返回惰性类,并将惰性类转换为结果标量类型会触发计算。这主要是可行的,但是在尝试使用区间算术(boost::numeric::interval)进行稳健计算时我遇到了问题。在这种情况下,完成计算的interval<double>类型定义了泛型模板构造函数和赋值运算符,这会导致编译麻烦,因为泛型模板优先于隐式转换,尽管模板无法编译:它的原型是有效的。这是一个解决该问题的最小示例:
#include <iostream>
//some generic costly operation that I'd like to avoid
template<typename T>
T complicated_stuff(const T& t) {
std::cout << "computing" << std::endl ;
return t*t ;
}
//implementing a lazy version through a convertible return type
template<typename T>
struct Lazy {
Lazy(const T& t_in) : t(t_in) {}
//the computation only happens when converted
operator T() const {
t = complicated_stuff(t) ;
return t ;
}
mutable T t ;
} ;
//generically wrapping the lazy implementation
template<typename T>
Lazy<T> lazy_complicated_stuff(const T& t) {
return Lazy<T>(t) ;
}
//the desired object type to run the computation on
//I can't modify CustomScalar, boost::numeric::interval in my use case
struct CustomScalar {
CustomScalar() {}
CustomScalar(const CustomScalar&) = default ;
//this generates my construction trouble
template<typename T>
CustomScalar(const T& v) : val(v) {}
CustomScalar& operator=(const CustomScalar&) = default ;
//this generates my assignment problems
template<typename T>
CustomScalar& operator=(const T& t) { val = t ; return *this ; }
//for the complicated stuff to work
CustomScalar operator*(const CustomScalar& rhs) const {
return CustomScalar(val*rhs.val) ;
}
float val ;
} ;
std::ostream& operator<<(std::ostream& out, CustomScalar s) {
out << s.val ;
return out ;
}
//some function that requires the value and therefore the computation
template<typename T>
void print(const T& t) {
std::cout << "printing" << std::endl ;
std::cout << t << std::endl ;
}
int main()
{
//some initial custom scalar
CustomScalar s0(10) ;
print(s0) ;
//ground truth computation
auto s1 = complicated_stuff(s0) ;
print(s1) ;
//lazy version computed during print
auto s2 = lazy_complicated_stuff(s0) ;
print(s2) ;
//reassigning s2
s2 = lazy_complicated_stuff(s1) ;
print(s2) ;
//now trying to reassign s1 with some lazy stuff
//desired syntax, doesn't work unless no tempate operator=
//s1 = lazy_complicated_stuff(s1) ;
//clumsy, not working unless no template constructor
//s1 = CustomScalar(lazy_complicated_stuff(s1)) ;
//unsatisfactorily working
//because both possible constructors require implicit conversion ?
//how is the priority defined then ? The non template overload ?
CustomScalar tmp = lazy_complicated_stuff(s1) ;
s1 = tmp ;
print(s1) ;
return 0 ;
}
我不确定为什么最终不能令人满意的工作版本可以工作,并且不能令人满意,因为我不能通用地使用我的惰性类型作为原始标量类型的替代品:使用 i 的代码必须精心设计以解决分配和构造问题。
我没有一个优雅的解决方案来解决这个问题。目前,我最好的选择似乎是将 boost::numeric::interval 类型包装在一些自定义对象中以 SFINAE 删除通用模板构造函数,但我很好奇是否可以找到更优雅的解决方案。
【问题讨论】:
-
你不能有
Lazy<T>::evaluate()吗?然后s1 = lazy_complicated_stuff(s1).evaluate(); -
@Jarod42 是个好主意,但是我的复杂函数是在更广泛的代码库中作为通用回调提供的,透明性背后的想法是,只需替换回调就可以提供惰性而不修改更广泛的代码基地。
-
我当前的hacky版本是在我的代码中定义
operator=的重载CustomScalar。给定模板的原型,我可以决定提供一个template<> CustomScalar& operator=< Lazy<CustomScalar> >(const Lazy<CustomScalar>&)定义。但这似乎很危险。
标签: c++ templates lazy-evaluation