无法推断模板参数?

时间:2011-08-08 12:54:44

标签: c++ templates

我有以下课程

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; }

};

尝试在类

之外定义模板运算符时会出现问题

4 个答案:

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