无法推导模板参数

could not deduce template argument?

本文关键字:参数      更新时间:2023-10-16

我有以下类

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)
{
...
...
...