我有以下课程
template<typename hi_t, typename lo_t>
struct int_t
{
hi_t hi;
lo_t lo;
int_t() : lo(0), hi(0) {}
int_t(int value) : lo(value), hi( value<0u? -1: 0 ) {}
int_t(unsigned value) : lo(value), hi( 0 ) {}
int_t& operator+=(const int_t& rhs)
{
lo_t _lo = lo;
lo += rhs.lo;
hi += rhs.hi;
hi += (int)(lo < _lo);
return *this;
}
template<typename hi_t, typename lo_t>
inline friend int_t<hi_t, lo_t> operator+(const int_t<hi_t, lo_t>&, const int_t<hi_t, lo_t>&);
};
template<typename hi_t, typename lo_t>
int_t<hi_t, lo_t> operator+(const int_t<hi_t, lo_t>& lhs, const int_t<hi_t, lo_t>& rhs)
{ return int_t<hi_t, lo_t>(lhs) += rhs; }
执行以下代码时
typedef int_t<long long, unsigned long long> int128;
int main()
{
int128 i = 1024;
i = i + 20;
}
编译器产生错误:
'int_t<hi_t,lo_t> operator +(const int_t<hi_t,lo_t> &,const int_t<hi_t,lo_t> &)' : could not deduce template argument for 'const int_t<hi_t,lo_t> &' from 'int'
当我把模板操作符的代码放在类体中时 - 从友元操作符中删除模板行 - 它可以工作,但是在类外的朋友操作符它不能推断出操作符。我认为当编译器为此模板运算符生成代码时,输入参数和返回值将为int128
类型,因此从int转换为该类型应该没有问题。
更新
如果我们在类中定义了friend运算符,就像上面的例子一样工作
template<typename hi_t, typename lo_t>
struct int_t
{
hi_t hi;
lo_t lo;
int_t() : lo(0), hi(0) {}
int_t(int value) : lo(value), hi( value<0u? -1: 0 ) {}
int_t(unsigned value) : lo(value), hi( 0 ) {}
int_t& operator+=(const int_t& rhs)
{
lo_t _lo = lo;
lo += rhs.lo;
hi += rhs.hi;
hi += (int)(lo < _lo);
return *this;
}
friend int_t operator+(const int_t& lhs, const int_t& rhs)
{ return int_t(lhs) += rhs; }
};
尝试在类
之外定义模板运算符时会出现问题答案 0 :(得分:4)
代码比起初看起来更棘手。最棘手的部分是你的朋友功能声明。你应该看看这个answer关于从模板中与一个函数建立联系的问题。简短的建议是删除模板operator+
并将其实现为 in 类声明中的非模板友元函数:
template<typename hi_t, typename lo_t>
struct int_t
{
// ...
friend int_t operator+(int_t lhs, const int_t& rhs ) {
return lhs+=rhs;
}
};
截至特定错误,它可能没那么有用,甚至可能会让人感到困惑,但您可以首先考虑到,如果在类型扣除之后,模板只会被考虑用于运算符重载。完美匹配(即不需要转换)。这意味着int128_t + int
将永远不会与左右两侧具有相同类型的模板化operator+
匹配,即使存在转化,
上面提出的解决方案声明(并定义)非模板函数。因为它是在类中定义的,所以它只会被Argument Dependent Lookup考虑,因此仅在其中一个运算符为int_t
时才会应用,如果ADL找到它,那么它将被选中使用通常的非模板规则重载解析,这意味着编译器能够对lhs或rhs使用任何可能的转换(如果ADL找到它们中的一个必须是int_t
实例,但它将转换另一个)。
答案 1 :(得分:1)
打开所有编译器警告。
您在friend
声明中使用与模板类本身相同的模板参数名称,这是不好的;重命名它们。这是一个解决方案:删除外部运算符定义并进行内联定义:
template<typename H, typename L>
inline friend int_t operator+(const int_t & lhs, const int_t<H, L> & rhs)
{
return int_t(lhs) += rhs;
}
现在,由于你的RHS是一种任意类型,你必须提到类型:
i = i + int128(20);
这是因为无法从整数H,L
中推导出参数20
,因此可以执行int_t<H,L>(20)
的适当转换(请参阅Nawaz的回答)!
要利用int
中的转化构造函数,您只能使用相同的类型,而不是模板化的其他类型。为此,添加一个非模板运算符:
int_t operator+(const int_t & rhs) const { return int_t(*this) += rhs; }
现在您可以使用i = i + 20;
构造函数来说int_t(int)
。
更新:正如OP建议的那样,为了允许对称调用(i = 50 + i;
),并且为了仅允许大卫建议的固定类型内的操作,我们应该同时删除一元运算符和模板化的朋友二元运算符,而只是有一个非模板化的二元朋友:
friend int_t operator+(const int_t & lhs, const int_t & rhs) { return int_t(lhs) += rhs; }
这是设计选择的问题;我个人赞成最终版本。
答案 2 :(得分:0)
您确定operator+=
应该是会员模板吗?通常,你只是
inline friend int_t operator+(const int_t&, const int_t&) {...}
答案 3 :(得分:-1)
我不确定但是编译器可能需要再次使用模板参数
...
...
...
template<typename hi_t, typename lo_t>
int_t& operator+=(const int_t& rhs)
{
...
...
...