【问题标题】:priority of implicit conversion over template constructor and assignment operator for transparent lazyness隐式转换优先于模板构造函数和赋值运算符以实现透明惰性
【发布时间】: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&lt;T&gt;::evaluate()吗?然后s1 = lazy_complicated_stuff(s1).evaluate();
  • @Jarod42 是个好主意,但是我的复杂函数是在更广泛的代码库中作为通用回调提供的,透明性背后的想法是,只需替换回调就可以提供惰性而不修改更广泛的代码基地。
  • 我当前的hacky版本是在我的代码中定义operator=的重载CustomScalar。给定模板的原型,我可以决定提供一个template&lt;&gt; CustomScalar&amp; operator=&lt; Lazy&lt;CustomScalar&gt; &gt;(const Lazy&lt;CustomScalar&gt;&amp;) 定义。但这似乎很危险。

标签: c++ templates lazy-evaluation


【解决方案1】:

问题源于 s1 的类型。主要:

//...
CustomScalar s0(10) ;

//...    
auto s1 = complicated_stuff(s0) ;

// this resolves to:
CustomScalar s1 = complicated_stuff(s0) ;

一个简单的解决方案是为您的 CustomScalar 类定义 complex_stuff() 和 lazy_complicated_stuff() 的特定重载,如下所示:

inline CustomScalar complicated_stuff(const CustomScalar& s) 
{
    return complicated_stuff(s.val); // call the template function
}

inline CustomScalar lazy_complicated_stuff(const CustomScalar& s) 
{
    return lazy_complicated_stuff(s.val); // call the template function
}

在这里,它们返回一个 CustomScalar,但如果需要,这些重载可以改为返回一个普通的浮点数。

这应该以非侵入方式解决模板构造函数和赋值运算符的问题。

这里有一个沙盒可以玩代码:https://godbolt.org/z/K448jfEbv

【讨论】:

    猜你喜欢
    • 2023-03-10
    • 2014-02-17
    • 2020-09-06
    • 2010-11-25
    • 2020-09-04
    • 2012-10-05
    • 2012-06-27
    • 2017-07-04
    • 1970-01-01
    相关资源
    最近更新 更多