为什么会出现这种歧义?

2021-12-13 00:00:00 operator-overloading templates c++

考虑我有以下最少的代码:

#include 模板结构体数据{typedef typename boost::remove_extent::type value_type;ptr_t 数据;value_type &运算符 [] ( size_t id ) { 返回数据 [id];}运算符 ptr_t &() { 返回数据;}};int main( int argc, char ** argv ){TDatat;t[1][1] = 5;返回0;}

GNU C++ 给了我错误:

test.cpp: 在函数 'int main(int, char**)' 中:test.cpp:16: error: ISO C++ 说这些是模棱两可的,即使第一个最差的转换比第二个最差的转换要好:test.cpp:9: 注意: 候选 1: typename boost::remove_extent<ptr_t>::type&TData<ptr_t>::operator[](size_t) [with ptr_t = float [100][100]]test.cpp:16: 注意: 候选 2: operator[](float (*)[100], int) 

我的问题是:

  1. 为什么 GNU C++ 会出现错误,而英特尔 C++ 编译器不会?
  2. 为什么将 operator[] 改成下面这样会导致编译没有错误?<块引用>

    value_type &运算符 [] ( int id ) { 返回数据 [id];}

感谢 C++ 标准的链接.

<小时>

正如我在此处看到的,有两条转化路径:

  1. (1)intsize_t 和 (2)operator[](size_t).
  2. (1)operator ptr_t&(), (2)intsize_t 和 (3) 内置 operator[](size_t).

解决方案

其实很简单.对于 t[1],重载解析有以下候选:

Candidate 1(内置:13.6/13)(T 是某种任意对象类型):

  • 参数列表:(T*, ptrdiff_t)

候选人 2(您的操作员)

  • 参数列表:(TData&, something unsigned)

参数列表由13.3.1.2/6给出:

<块引用>

重载解析的候选函数集是成员候选、非成员候选和内置候选的联合.参数列表包含运算符的所有操作数.

  • 参数列表:(TData, int)

您看到第一个参数与候选 2 的第一个参数完全匹配.但它需要对候选 1 的第一个参数进行用户定义的转换.因此对于第一个参数,第二个候选获胜.

您还看到第二个位置的结果取决于.让我们做一些假设,看看我们得到了什么:

  1. ptrdiff_tint:第一个候选者获胜,因为它完全匹配,而第二个候选者需要一个整数转换.
  2. ptrdiff_tlong:两个候选人都赢了,因为两者都需要整数转换.

现在,13.3.3/1

<块引用>

让 ICSi(F) 表示隐式转换序列,将列表中的第 i 个参数转换为可行函数 F 的第 i 个参数的类型.

一个可行函数 F1 被定义为比另一个可行函数 F2 更好的函数,如果对于所有参数 i,ICSi(F1) 不是比 ICSi(F2) 更差的转换序列,然后......对于某些参数 j, ICSj(F1) 是比 ICSj(F2) 更好的转换序列,或者,如果不是......

对于我们的第一个假设,我们没有得到总冠军,因为候选人 2 赢得了第一个参数,而候选人 1 赢得了第二个参数.我称之为纵横交错.对于我们的第二个假设,候选 2 总体上获胜,因为这两个参数都没有更差的转换,但第一个参数的转换更好.

对于第一个假设,第二个参数中的整数转换(int 到 unsigned)比第一个参数中的另一个候选者的用户定义转换更不重要.在纵横交错中,规则是粗糙的.

<小时>

最后一点可能仍然让你感到困惑,因为周围都是大惊小怪,所以让我们举个例子

void f(int, int) { }void f(long, char) { }int main() { f(0, 'a');}

这给了你同样令人困惑的 GCC 警告(我记得,当我几年前第一次收到它时,实际上让我感到困惑),因为 0 转换为 long'a'int 更糟糕 - 但你会产生歧义,因为你处于纵横交错的情况.

Consider I have the following minimal code:

#include <boost/type_traits.hpp>

template<typename ptr_t>
struct TData
{
    typedef typename boost::remove_extent<ptr_t>::type value_type;
    ptr_t data;

    value_type & operator [] ( size_t id ) { return data[id]; }
    operator ptr_t & () { return data; }
};

int main( int argc, char ** argv )
{
    TData<float[100][100]> t;   
    t[1][1] = 5;
    return 0;
}

GNU C++ gives me the error:

test.cpp: In function 'int main(int, char**)':
test.cpp:16: error: ISO C++ says that these are ambiguous, even though the worst conversion for the first is better than the worst conversion for second:
test.cpp:9: note: candidate 1: typename boost::remove_extent<ptr_t>::type& TData<ptr_t>::operator[](size_t) [with ptr_t = float [100][100]]
test.cpp:16: note: candidate 2: operator[](float (*)[100], int) <built-in>

My questions are:

  1. Why GNU C++ gives the error, but Intel C++ compiler is not?
  2. Why changing operator[] to the following leads to compiling without errors?

    value_type & operator [] ( int id ) { return data[id]; }

Links to the C++ Standard are appreciated.


As I can see here are two conversion paths:

  1. (1)int to size_t and (2)operator[](size_t).
  2. (1)operator ptr_t&(), (2)int to size_t and (3)build-in operator[](size_t).

解决方案

It's actually quite straight forward. For t[1], overload resolution has these candidates:

Candidate 1 (builtin: 13.6/13) (T being some arbitrary object type):

  • Parameter list: (T*, ptrdiff_t)

Candidate 2 (your operator)

  • Parameter list: (TData<float[100][100]>&, something unsigned)

The argument list is given by 13.3.1.2/6:

The set of candidate functions for overload resolution is the union of the member candidates, the non-member candidates, and the built-in candidates. The argument list contains all of the operands of the operator.

  • Argument list: (TData<float[100][100]>, int)

You see that the first argument matches the first parameter of Candidate 2 exactly. But it needs a user defined conversion for the first parameter of Candidate 1. So for the first parameter, the second candidate wins.

You also see that the outcome of the second position depends. Let's make some assumptions and see what we get:

  1. ptrdiff_t is int: The first candidate wins, because it has an exact match, while the second candidate requires an integral conversion.
  2. ptrdiff_t is long: Neither candidate wins, because both require an integral conversion.

Now, 13.3.3/1 says

Let ICSi(F) denote the implicit conversion sequence that converts the i-th argument in the list to the type of the i-th parameter of viable function F.

A viable function F1 is defined to be a better function than another viable function F2 if for all arguments i, ICSi(F1) is not a worse conversion sequence than ICSi(F2), and then ... for some argument j, ICSj(F1) is a better conversion sequence than ICSj(F2), or, if not that ...

For our first assumption, we don't get an overall winner, because Candidate 2 wins for the first parameter, and Candidate 1 wins for the second parameter. I call it the criss-cross. For our second assumption, the Candidate 2 wins overall, because neither parameter had a worse conversion, but the first parameter had a better conversion.

For the first assumption, it does not matter that the integral conversion (int to unsigned) in the second parameter is less of an evil than the user defined conversion of the other candidate in the first parameter. In the criss-cross, rules are crude.


That last point might still confuse you, because of all the fuss around, so let's make an example

void f(int, int) { }
void f(long, char) { }

int main() { f(0, 'a'); }

This gives you the same confusing GCC warning (which, I remember, was actually confusing the hell out of me when I first received it some years ago), because 0 converts to long worse than 'a' to int - yet you get an ambiguity, because you are in a criss-cross situation.

相关文章