C++ 为什么会出现这种歧义?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3519282/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Why is this ambiguity here?
提问by Kirill V. Lyadvinsky
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:
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>
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]; }
- 为什么 GNU C++ 给出错误,而 Intel C++ 编译器却没有?
- 为什么更改
operator[]
为以下会导致编译没有错误?value_type & operator [] ( int id ) { return data[id]; }
Links to the C++ Standard are appreciated.
对 C++ 标准的链接表示赞赏。
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)
.
- (1)
int
至size_t
(2)operator[](size_t)
。 - (1)
operator ptr_t&()
, (2)int
到size_t
和 (3) 内置operator[](size_t)
.
采纳答案by Johannes Schaub - litb
It's actually quite straight forward. For t[1]
, overload resolution has these candidates:
这实际上很简单。对于t[1]
,重载解析有以下候选:
Candidate 1 (builtin: 13.6/13)(T being some arbitrary object type):
候选 1(内置:13.6/13)(T 是某种任意对象类型):
- Parameter list:
(T*, ptrdiff_t)
- 参数列表:
(T*, ptrdiff_t)
Candidate 2 (your operator)
候选人 2(您的接线员)
- Parameter list:
(TData<float[100][100]>&, something unsigned)
- 参数列表:
(TData<float[100][100]>&, something unsigned)
The argument list is given by 13.3.1.2/6
:
参数列表由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)
- 参数列表:
(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.
您会看到第一个参数与 Candidate 2 的第一个参数完全匹配。但是它需要对候选 1 的第一个参数进行用户定义的转换。因此对于第一个参数,第二个候选获胜。
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.
ptrdiff_t
isint
:第一个候选者获胜,因为它具有完全匹配,而第二个候选者需要整数转换。ptrdiff_t
islong
:两个候选人都不会获胜,因为两者都需要一个整数转换。
Now, 13.3.3/1
says
现在,13.3.3/1
说
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 ...
令 ICSi(F) 表示隐式转换序列,将列表中的第 i 个参数转换为可行函数 F 的第 i 个参数的类型。
一个可行函数 F1 被定义为比另一个可行函数 F2 更好的函数,如果对于所有参数 i,ICSi(F1) 不是比 ICSi(F2) 更差的转换序列,然后......对于某些参数 j,ICSj( F1) 是比 ICSj(F2) 更好的转换序列,或者,如果不是……
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 betterconversion.
对于我们的第一个假设,我们没有得到总冠军,因为候选人 2 赢得了第一个参数,而候选人 1 赢得了第二个参数。我称之为十字交叉。对于我们的第二个假设,候选 2 总体上获胜,因为这两个参数都没有更差的转换,但第一个参数的转换更好。
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.
对于第一个假设,第二个参数中的整数转换(int 到 unsigned)比第一个参数中的另一个候选者的用户定义转换更不坏并不重要。在纵横交错中,规则是粗糙的。
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.
这给了你同样令人困惑的 GCC 警告(我记得,当我几年前第一次收到它时,它实际上让我感到困惑),因为0
转换为long
比'a'
to更糟糕int
- 但你会产生歧义,因为你处于纵横交错的局面。
回答by David Rodríguez - dribeas
With the expression:
用表达式:
t[1][1] = 5;
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)]
编译器必须关注左侧以确定那里有什么,因此在= 5;
解析 lhs 之前忽略 。留给我们表达式: t[1][1]
,它代表两个操作,第二个操作来自第一个操作的结果,因此编译器必须只考虑表达式的第一部分:t[1]
。实际类型是(TData&)[(int)]
The call does not match exactly any functions, as operator[]
for TData
is defined as taking a size_t
argument, so to be able to use it the compiler would have to convert 1
from int
to size_t
with an implicit conversion. That is the first choice. Now, another possible path is applying user defined conversion to convert TData<float[100][100]>
into float[100][100]
.
呼叫不完全匹配的任何功能,如operator[]
对TData
被定义为取一个size_t
参数,因此能够使用它的编译器必须转换1
来自int
于size_t
同隐式转换。那是第一选择。现在,另一种可能的路径是应用用户定义的转换来转换TData<float[100][100]>
为float[100][100]
.
The int
to size_t
conversion is an integral conversionand is ranked as Conversionin Table 9 of the standard, as is the user defined conversionfrom TData<float[100][100]>
to float[100][100]
conversion according to §13.3.3.1.2/4. The conversion from float [100][100]&
to float (*)[100]
is ranked as Exact Matchin Table 9. The compiler is not allowed to choose from those two conversion sequences.
的int
到size_t
转换是一个整体的转换被列为转换在标准的表9中,由于是用户定义的转换从TData<float[100][100]>
到float[100][100]
根据§13.3.3.1.2/ 4转换。从float [100][100]&
to的转换在表 9 中float (*)[100]
被列为Exact Match。不允许编译器从这两个转换序列中进行选择。
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.
Q1:并非所有编译器都以相同的方式遵守标准。发现在某些特定情况下编译器的性能与其他情况不同是很常见的。在这种情况下,g++ 实现者决定抱怨标准不允许编译器选择,而英特尔实现者可能只是默默地应用了他们首选的转换。
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 for t.operator[](1)
with no conversions whatsoever, so the compiler must follow that path.
Q2:当您更改用户定义的签名时operator[]
,参数与传入的类型完全匹配。t[1]
是完美匹配t.operator[](1)
,没有任何转换,因此编译器必须遵循该路径。
回答by adf88
I don't know what's the exact answer, but...
我不知道确切的答案是什么,但是...
Because of this operator:
因为这个运营商:
operator ptr_t & () { return data; }
there exist already built-in []
operator (array subscription) which accepts size_t
as index. So we have two []
operators, the built-in and defined by you. Booth accepts size_t
so this is considered as illegal overload probably.
已经存在[]
接受size_t
作为索引的内置运算符(数组订阅)。所以我们有两个[]
运算符,内置的和由您定义的。Booth接受size_t
所以这很可能被认为是非法超载。
//EDIT
this should work as you intended
//编辑
这应该按你的预期工作
template<typename ptr_t>
struct TData
{
ptr_t data;
operator ptr_t & () { return data; }
};
回答by UncleBens
It seems to me that with
在我看来,与
t[1][1] = 5;
the compiler has to choose between.
编译器必须在两者之间做出选择。
value_type & operator [] ( size_t id ) { return data[id]; }
which would match if the int
literal were to be converted to size_t
, or
如果将int
文字转换为size_t
,则匹配,或
operator ptr_t & () { return data; }
followed 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.
至于错误,似乎 GCC 作为编译器扩展希望为您选择第一个重载,并且您正在使用 -pedantic 和/或 -Werror 标志进行编译,这迫使它遵守标准的规定。
(I'm not in a -pedantic mood, so no quotes from the standard, especially on this topic.)
(我没有学究气,所以没有引用标准,尤其是在这个主题上。)
回答by Chubsdad
I have tried to show the two candidates for the expression t[1][1]. These are both of equal RANK (CONVERSION). Hence ambiguity
我试图展示表达式 t[1][1] 的两个候选对象。这些都是相同的排名(转换)。因此歧义
I think the catch here is that the built-in [] operator as per 13.6/13 is defined as
我认为这里的问题是根据 13.6/13 的内置 [] 运算符定义为
T& operator[](T*, ptrdiff_t);
On my system ptrdiff_t is defined as 'int' (does that explain x64 behavior?)
在我的系统上,ptrdiff_t 被定义为“int”(这能解释 x64 的行为吗?)
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; }
};
typedef float (&ATYPE) [100][100];
int main( int argc, char ** argv )
{
TData<float[100][100]> t;
t[size_t(1)][size_t(1)] = 5; // note the cast. This works now. No ambiguity as operator[] is preferred over built-in operator
t[1][1] = 5; // error, as per the logic given below for Candidate 1 and Candidate 2
// Candidate 1 (CONVERSION rank)
// User defined conversion from 'TData' to float array
(t.operator[](1))[1] = 5;
// Candidate 2 (CONVERSION rank)
// User defined conversion from 'TData' to ATYPE
(t.operator ATYPE())[1][1] = 6;
return 0;
}
EDIT:
编辑:
Here is what I think:
这是我的想法:
For candidate 1 (operator []) the conversion sequence S1 is User defined conversion - Standard Conversion (int to size_t)
对于候选 1(运算符 []),转换序列 S1 是用户定义的转换 - 标准转换(int 到 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)
对于候选 2,转换序列 S2 是用户定义的转换 -> int 到 ptrdiff_t(对于第一个参数) -> int 到 ptrdiff_t(对于第二个参数)
The conversion sequence S1 is a subset of S2 and is supposed to be better. But here is the catch...
转换序列 S1 是 S2 的子集,应该更好。但这里有一个问题......
Here the below quote from Standard should help.
此处来自标准的以下引用应该有所帮助。
$13.3.3.2/3 states - Standard conversion sequence S1 is a better conversion sequence than standard conversion sequence S2 if — S1 is a proper subsequence of S2 (comparing the conversion sequences in the canonical form defined by 13.3.3.1.1, excluding any Lvalue Transformation; the identity conversion sequence is considered to be a subsequence of any non-identity conversion sequence) or, if not that...
$13.3.3.2 states- " User-defined conversion sequence U1 is a better conversion sequence than another user-defined conversion sequence U2 if they contain the same user-defined conversion function or constructor and if the second standard conversion sequence of U1 is better than the second standard conversion sequence of U2."
$13.3.3.2/3 状态 - 标准转换序列 S1 是比标准转换序列 S2 更好的转换序列,如果 — S1 是 S2 的适当子序列(比较 13.3.3.1.1 定义的规范形式的转换序列,不包括任何左值转换;恒等转换序列被认为是任何非恒等转换序列的子序列),或者,如果不是……
$13.3.3.2 states-" 如果用户定义的转换序列 U1 包含相同的用户定义的转换函数或构造函数,并且如果 U1 的第二个标准转换序列优于另一个用户定义的转换序列 U2,则用户定义的转换序列 U1 是比另一个用户定义的转换序列 U2 更好的转换序列U2的第二个标准转换序列。”
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.
这里和条件“如果它们包含相同的用户定义的转换函数或构造函数”的第一部分不成立。因此,即使和条件的第二部分“如果 U1 的第二个标准转换序列比 U2 的第二个标准转换序列更好。”成立,但 S1 和 S2 都不优于另一个。
That's why gcc's phantomerror 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"
这就是为什么 gcc 的幻象错误消息“ISO C++ 说这些是模棱两可的,即使第一个最差的转换比第二个最差的转换要好”
This explains the ambiguity quiet well IMHO
这解释了含糊不清的安静恕我直言
回答by Potatoswatter
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 type int
but most variables you'll be using aren't), maybe you can generalize it:
过载解决是一个令人头疼的问题。但是由于您偶然发现了一个operator[]
对示例过于具体的修复(消除了将索引操作数转换为)(文字是类型,int
但您将使用的大多数变量不是),也许您可以概括它:
template< typename IT>
typename boost::enable_if< typename boost::is_integral< IT >::type, value_type & >::type
operator [] ( IT id ) { return data[id]; }
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.
不幸的是,我无法对此进行测试,因为 GCC 4.2.1 和 4.5 在--pedantic
. 这确实提出了一个问题,它是否是编译器错误。
Also, once I eliminated the Boost dependency, it passed Comeau.
此外,一旦我消除了 Boost 依赖,它就通过了 Comeau。