无法推导模板参数
could not deduce template argument?
我有以下类
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'
当我把template运算符的代码放在类主体内时——从友元运算符中删除template行——它是有效的,但友元运算符在类外,它无法推导出运算符。我认为当编译器为这个模板运算符生成代码时,输入参数和返回值的类型将是int128
,所以从int转换为该类型应该没有问题。
更新
如果我们在类中定义友元运算符,如下前一个例子所示
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; }
};
当试图在类之外定义模板运算符时会出现问题
代码比最初看起来更棘手。最棘手的部分是友元函数的声明。你应该看看这个关于从模板中交友函数的答案。简短的建议是删除模板化的operator+
,并将其作为类声明中的非模板友元函数来实现:
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
实例化,但它将转换另一个(。
打开所有编译器警告。
您在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);
这是因为没有办法从整数20
推导出参数H,L
,从而可以执行到int_t<H,L>(20)
的适当转换(请参阅Nawaz的回答(!
要利用int
中的转换构造函数,您只能对同一类型进行操作,而不能对模板化的其他类型进行操作。为此,添加一个非模板运算符:
int_t operator+(const int_t & rhs) const { return int_t(*this) += rhs; }
现在可以使用int_t(int)
构造函数说出i = i + 20;
。
更新:正如OP所建议的,为了允许对称调用(i = 50 + i;
(,并且像David所建议的那样,为了只允许固定类型内的操作,我们应该删除一元运算符和模板化的友元二进制运算符,而只使用非模板化的二进制友元:
friend int_t operator+(const int_t & lhs, const int_t & rhs) { return int_t(lhs) += rhs; }
这是设计选择的问题;我个人赞成最后版本。
您确定operator+=
应该是成员模板吗?通常,你只是
inline friend int_t operator+(const int_t&, const int_t&) {...}
我不确定,但编译器可能需要再次使用模板参数
...
...
...
template<typename hi_t, typename lo_t>
int_t& operator+=(const int_t& rhs)
{
...
...
...
- 如何反转整数参数包
- 使用C++库在Android项目中修改gradle中的cmake参数,用于插入指令的测试
- 如何使用默认参数等选择模板专业化
- 模板参数替换失败,并且未完成隐式转换
- 具有默认模板参数的多态类的模板推导失败
- lambda参数转换为constexpr技巧,然后获取带链接的数组
- 将数组作为参数传递给函数安全吗?作为第三方职能部门,可以探索他们想要的之外的其他元素
- 函数调用中参数的顺序重要吗
- 部分定义/别名模板模板参数
- 模板-模板参数推导:三个不同的编译器三种不同的行为
- 使用不带参数的函数访问结构元素
- 基于另一个成员参数将函数调用从类传递给它的一个成员
- 如何在OMNET++中指定与命令行参数组合的输出文件名
- 如何使用Luacneneneba API正确读取字符串和表参数
- 在派生函数中指定void*参数
- 视图中的参数推导失败:take_while
- static_assert在宏中,但也可以扩展到可以用作函数参数的东西
- 使用指向成员的指针将成员函数作为参数传递
- 没有名称的C++模板参数
- 如何将enable-if与模板参数和参数包一起使用