为什么这里有这样的歧义?
考虑我有以下最少代码:
#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++ 给我错误:
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>
我的问题是:
- 为什么 GNU C++ 给出错误,但 Intel C++ 编译器却没有?
- 为什么将
operator[]
更改为以下内容会导致编译没有错误? <块引用>值类型&运算符[] (int id) { 返回数据[id]; }
非常感谢 C++ 标准的链接。
正如我所看到的,这里有两个转换路径:
- (1)
int
到size_t
和 (2)operator[](size_t)
。 - (1)
operator ptr_t&()
、(2)int
到size_t
和 (3)内置operator[]( size_t)
。
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:
- Why GNU C++ gives the error, but Intel C++ compiler is not?
- 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)
int
tosize_t
and (2)operator[](size_t)
. - (1)
operator ptr_t&()
, (2)int
tosize_t
and (3)build-inoperator[](size_t)
.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
实际上非常简单。对于
t[1]
,重载解析有以下候选:候选 1(内置:13.6/13)(T 是任意对象类型):
(T*, ptrdiff_t)
候选 2(您的操作员)
(TData&, some unsigned)
参数列表由
13.3.1.2/6
给出:(TData, int)
您会看到第一个参数与候选 2 的第一个参数完全匹配。但它需要对候选 1 的第一个参数进行用户定义的转换。因此,对于第一个参数,第二个候选获胜。
您还可以看到第二个位置的结果取决于。让我们做一些假设,看看我们得到什么:
ptrdiff_t
isint
:第一个候选者获胜,因为它具有精确匹配,而第二个候选者需要整数转换。ptrdiff_t
islong
:两个候选人都没有获胜,因为两者都需要整数转换。现在,
13.3.3/1
说对于我们的第一个假设,我们没有获得总体获胜者,因为候选者 2 赢得了第一个参数,而候选者 1 赢得了第二个参数。我称之为纵横交错。对于我们的第二个假设,候选者 2 总体获胜,因为两个参数都没有更差的转换,但第一个参数有更好的转换。
对于第一个假设,第二个参数中的积分转换(int 到无符号)比第一个参数中其他候选的用户定义转换的危害要小,这并不重要。在十字路口,规则是粗暴的。
最后一点可能仍然会让你感到困惑,因为周围有很多大惊小怪的事情,所以让我们举一个例子
这给了你同样令人困惑的 GCC 警告(我记得,当我几年前第一次收到它时,它实际上让我感到困惑) ),因为
0
转换为long
比'a'
转换为int
更糟糕 - 但你会得到一个歧义,因为你正处于十字路口的境地。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):
(T*, ptrdiff_t)
Candidate 2 (your operator)
(TData<float[100][100]>&, something unsigned)
The argument list is given by
13.3.1.2/6
:(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:
ptrdiff_t
isint
: The first candidate wins, because it has an exact match, while the second candidate requires an integral conversion.ptrdiff_t
islong
: Neither candidate wins, because both require an integral conversion.Now,
13.3.3/1
saysFor 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
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 tolong
worse than'a'
toint
- yet you get an ambiguity, because you are in a criss-cross situation.使用表达式:
编译器必须关注左侧以确定那里的内容,因此
= 5;
将被忽略,直到 lhs 被解析。剩下的表达式是:t[1][1]
,它代表两个操作,第二个操作对第一个操作的结果进行操作,因此编译器必须只考虑第一部分表达式的:t[1]
。实际类型为(TData&)[(int)]
该调用不完全匹配任何函数,如
operator
被定义为采用TData
的 []size_t
参数,因此为了能够使用它,编译器必须转换1
> 通过隐式转换从int
到size_t
。这是第一选择。现在,另一个可能的路径是应用用户定义的转换将TData
转换为float[100][100]
。int
到size_t
的转换是积分转换,并且在以下位置中排名为转换:标准的表 9,是从TData
到float[100][ 的用户定义转换 100]
根据 §13.3.3.1.2/4 进行转换。从float [100][100]&
到float (*)[100]
的转换在表 9 中被列为精确匹配。不允许编译器从这两个转换序列中进行选择。Q1:并非所有编译器都以相同的方式遵守该标准。在某些特定情况下,编译器的执行方式与其他编译器不同是很常见的。在这种情况下,g++ 实现者决定抱怨标准不允许编译器进行选择,而 Intel 实现者可能只是默默地应用他们首选的转换。
Q2:当您更改用户定义的
operator[]
的签名时,参数与传入的类型完全匹配。t[1]
与t.operator[](1)
完美匹配,无需进行任何转换,因此编译器必须遵循该路径。With the expression:
The compiler must focus on the left hand side to determine what goes there, so the
= 5;
is ignored until the lhs is resolved. Leaving us with the expression:t[1][1]
, which represents two operations, with the second one operating on the result from the first one, so the compiler must only take into account the first part of the expression:t[1]
.The actual type is(TData&)[(int)]
The call does not match exactly any functions, as
operator[]
forTData
is defined as taking asize_t
argument, so to be able to use it the compiler would have to convert1
fromint
tosize_t
with an implicit conversion. That is the first choice. Now, another possible path is applying user defined conversion to convertTData<float[100][100]>
intofloat[100][100]
.The
int
tosize_t
conversion is an integral conversion and is ranked as Conversion in Table 9 of the standard, as is the user defined conversion fromTData<float[100][100]>
tofloat[100][100]
conversion according to §13.3.3.1.2/4. The conversion fromfloat [100][100]&
tofloat (*)[100]
is ranked as Exact Match in Table 9. The compiler is not allowed to choose from those two conversion sequences.Q1: Not all compilers adhere to the standard in the same way. It is quite common to find out that in some specific cases a compiler will perform differently than the others. In this case, the g++ implementors decided to whine about the standard not allowing the compiler to choose, while the Intel implementors probably just silently applied their preferred conversion.
Q2: When you change the signature of the user defined
operator[]
, the argument matches exactly the passed in type.t[1]
is a perfect match fort.operator[](1)
with no conversions whatsoever, so the compiler must follow that path.我不知道确切的答案是什么,但是...
因为这个运算符:
已经存在内置的
[]
运算符(数组订阅),它接受size_t
作为指数。所以我们有两个[]
运算符,内置的和由您定义的。 Booth 接受size_t
因此这可能被视为非法超载。//编辑
这应该按你的预期工作
I don't know what's the exact answer, but...
Because of this operator:
there exist already built-in
[]
operator (array subscription) which acceptssize_t
as index. So we have two[]
operators, the built-in and defined by you. Booth acceptssize_t
so this is considered as illegal overload probably.//EDIT
this should work as you intended
在我看来,
必须在与编译器之间做出选择。
如果将
int
文字转换为size_t
,或者后跟普通数组索引,则该值将匹配,在这种情况下,索引的类型完全匹配。
至于错误,GCC 作为编译器扩展似乎会为您选择第一个重载,并且您正在使用 -pedantic 和/或 -Werror 标志进行编译,这迫使它坚持标准的字样。
(我没有迂腐的情绪,所以没有引用标准,特别是在这个主题上。)
It seems to me that with
the compiler has to choose between.
which would match if the
int
literal were to be converted tosize_t
, orfollowed by normal array indexing, in which case the type of the index matches exactly.
As to the error, it seems GCC as a compiler extension would like to choose the first overload for you, and you are compiling with the -pedantic and/or -Werror flag which forces it to stick to the word of the standard.
(I'm not in a -pedantic mood, so no quotes from the standard, especially on this topic.)
我试图显示表达式 t[1][1] 的两个候选值。它们的等级(转换)相同。因此,含糊之处
我认为这里的问题是,按照 13.6/13 的内置 [] 运算符被定义为
在我的系统上 ptrdiff_t 被定义为“int”(这是否解释了 x64 行为?)
编辑:
这是我的想法:
对于候选 1(运算符 []),转换序列 S1 为
用户定义的转换 - 标准转换(int 到 size_t)
对于候选 2,转换序列 S2 为
用户定义的转换-> int 到 ptrdiff_t (第一个参数)-> int 到 ptrdiff_t (第二个参数)
转换序列 S1 是 S2 的子集,应该更好。但这里有一个问题......
下面来自标准的引用应该有所帮助。
这里 and 条件的第一部分“如果它们包含相同的用户定义的转换函数或构造函数”并不成立。因此,即使 and 条件的第二部分“< em>如果 U1 的第二个标准转换序列比 U2 的第二个标准转换序列更好。”成立,则 S1 和 S2 都不优于另一个。
这就是为什么 gcc 的 phantom错误消息“ISO C++ 说这些是不明确的,即使第一个的最差转换比第二个的最差转换要好”
这很好地解释了歧义性恕我直言
I have tried to show the two candidates for the expression t[1][1]. These are both of equal RANK (CONVERSION). Hence ambiguity
I think the catch here is that the built-in [] operator as per 13.6/13 is defined as
On my system ptrdiff_t is defined as 'int' (does that explain x64 behavior?)
EDIT:
Here is what I think:
For candidate 1 (operator []) the conversion sequence S1 is
User defined conversion - Standard Conversion (int to size_t)
For candidate 2, the conversion sequence S2 is
User defined conversion -> int to ptrdiff_t (for first argument) -> int to ptrdiff_t (for second argument)
The conversion sequence S1 is a subset of S2 and is supposed to be better. But here is the catch...
Here the below quote from Standard should help.
Here the first part of the and condition "if they contain the same user-defined conversion function or constructor" does not hold good. So, even if the second part of the and condition "if the second standard conversion sequence of U1 is better than the second standard conversion sequence of U2." holds good, neither S1 nor S2 is preferred over the other.
That's why gcc's phantom error message "ISO C++ says that these are ambiguous, even though the worst conversion for the first is better than the worst conversion for the second"
This explains the ambiguity quiet well IMHO
过载解析是一个令人头疼的问题。但是,由于您偶然发现了一个修复(消除了索引操作数到
operator[]
的转换),该修复对于示例来说太具体了(文字是int
类型,但大多数变量您'将使用不是),也许您可以概括它:不幸的是,我无法测试这一点,因为 GCC 4.2.1 和 4.5 接受您的示例,而不会在
--pedantic
下抱怨。这确实提出了一个问题:这是否是编译器错误。而且,一旦我消除了 Boost 依赖,它就通过了 Comeau。
Overload resolution is a headache. But since you stumbled on a fix (eliminate conversion of the index operand to
operator[]
) which is too specific to the example (literals are typeint
but most variables you'll be using aren't), maybe you can generalize it:Unfortunately I can't test this because GCC 4.2.1 and 4.5 accept your example without complaint under
--pedantic
. Which really raises the question whether it's a compiler bug or not.Also, once I eliminated the Boost dependency, it passed Comeau.