为什么这里有这样的歧义?

发布于 2024-09-15 05:07:01 字数 1454 浏览 3 评论 0原文

考虑我有以下最少代码:

#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>

我的问题是:

  1. 为什么 GNU C++ 给出错误,但 Intel C++ 编译器却没有?
  2. 为什么将 operator[] 更改为以下内容会导致编译没有错误? <块引用>

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

非常感谢 C++ 标准的链接。


正如我所看到的,这里有两个转换路径:

  1. (1)intsize_t 和 (2)operator[](size_t)
  2. (1)operator ptr_t&()、(2)intsize_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:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

做个少女永远怀春 2024-09-22 05:07:01

实际上非常简单。对于 t[1],重载解析有以下候选:

候选 1(内置:13.6/13)(T 是任意对象类型):

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

候选 2(您的操作员)

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

参数列表由13.3.1.2/6给出:

重载决策的候选函数集是成员候选函数、非成员候选函数和内置候选函数的并集。参数列表包含运算符的所有操作数。

  • 参数列表:(TData, int)

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

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

  1. ptrdiff_t is int:第一个候选者获胜,因为它具有精确匹配,而第二个候选者需要整数转换。
  2. ptrdiff_t is long:两个候选人都没有获胜,因为两者都需要整数转换。

现在,13.3.3/1

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

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

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

对于第一个假设,第二个参数中的积分转换(int 到无符号)比第一个参数中其他候选的用户定义转换的危害要小,这并不重要。在十字路口,规则是粗暴的。


最后一点可能仍然会让你感到困惑,因为周围有很多大惊小怪的事情,所以让我们举一个例子

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

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

这给了你同样令人困惑的 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):

  • 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.

回梦 2024-09-22 05:07:01

使用表达式:

t[1][1] = 5;

编译器必须关注左侧以确定那里的内容,因此 = 5; 将被忽略,直到 lhs 被解析。剩下的表达式是:t[1][1],它代表两个操作,第二个操作对第一个操作的结果进行操作,因此编译器必须只考虑第一部分表达式的:t[1]。实际类型为(TData&)[(int)]

该调用不完全匹配任何函数,如operator TData 的 [] 被定义为采用 size_t 参数,因此为了能够使用它,编译器必须转换 1 > 通过隐式转换从 intsize_t。这是第一选择。现在,另一个可能的路径是应用用户定义的转换将 TData 转换为 float[100][100]

intsize_t 的转换是积分转换,并且在以下位置中排名为转换:标准的表 9,是从 TDatafloat[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:

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)]

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].

The int to size_t conversion is an integral conversion and is ranked as Conversion in Table 9 of the standard, as is the user defined conversion from 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 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 for t.operator[](1) with no conversions whatsoever, so the compiler must follow that path.

却一份温柔 2024-09-22 05:07:01

我不知道确切的答案是什么,但是...

因为这个运算符:

operator ptr_t & () { return data; }

已经存在内置的 [] 运算符(数组订阅),它接受 size_t 作为指数。所以我们有两个 [] 运算符,内置的和由您定义的。 Booth 接受 size_t 因此这可能被视为非法超载。

//编辑
这应该按你的预期工作

template<typename ptr_t>
struct TData
{
    ptr_t data;
    operator ptr_t & () { return data; }
};

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.

//EDIT
this should work as you intended

template<typename ptr_t>
struct TData
{
    ptr_t data;
    operator ptr_t & () { return data; }
};
我的影子我的梦 2024-09-22 05:07:01

在我看来,

t[1][1] = 5;

必须在与编译器之间做出选择。

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

如果将 int 文字转换为 size_t,或者

operator ptr_t & () { return data; }

后跟普通数组索引,则该值将匹配,在这种情况下,索引的类型完全匹配。


至于错误,GCC 作为编译器扩展似乎会为您选择第一个重载,并且您正在使用 -pedantic 和/或 -Werror 标志进行编译,这迫使它坚持标准的字样。

(我没有迂腐的情绪,所以没有引用标准,特别是在这个主题上。)

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

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.

(I'm not in a -pedantic mood, so no quotes from the standard, especially on this topic.)

放赐 2024-09-22 05:07:01

我试图显示表达式 t[1][1] 的两个候选值。它们的等级(转换)相同。因此,含糊之处

我认为这里的问题是,按照 13.6/13 的内置 [] 运算符被定义为

T& operator[](T*, ptrdiff_t);

在我的系统上 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; 
}

编辑:

这是我的想法:

对于候选 1(运算符 []),转换序列 S1 为
用户定义的转换 - 标准转换(int 到 size_t)

对于候选 2,转换序列 S2 为
用户定义的转换-> int 到 ptrdiff_t (第一个参数)-> int 到 ptrdiff_t (第二个参数)

转换序列 S1 是 S2 的子集,应该更好。但这里有一个问题......

下面来自标准的引用应该有所帮助。

$13.3.3.2/3 个州 - 标准
转换顺序 S1 更好
转换顺序高于标准
转换序列 S2 if — S1 是
S2 的真子序列(比较
中的转换序列
13.3.3.1.1 定义的规范形式,
排除任何左值变换;
恒等转换序列为
被认为是任意的子序列
非恒等转换序列)或,
如果不是的话...

$13.3.3.2 状态-“用户定义
转换序列 U1 更好
转换顺序比另一个
用户定义的转换序列 U2 if
它们包含相同的用户定义
转换函数或构造函数和
如果第二个标准转换
U1 的序列优于
第二个标准转换序列
U2。”

这里 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

T& operator[](T*, ptrdiff_t);

On my system ptrdiff_t is defined as 'int' (does that explain x64 behavior?)

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)

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.

$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."

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

流年里的时光 2024-09-22 05:07:01

过载解析是一个令人头疼的问题。但是,由于您偶然发现了一个修复(消除了索引操作数到 operator[] 的转换),该修复对于示例来说太具体了(文字是 int 类型,但大多数变量您'将使用不是),也许您可​​以概括它:

template< typename IT>
typename boost::enable_if< typename boost::is_integral< IT >::type, value_type & >::type
operator [] ( IT id ) { return data[id]; }

不幸的是,我无法测试这一点,因为 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 type int but most variables you'll be using aren't), maybe you can generalize it:

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.

Also, once I eliminated the Boost dependency, it passed Comeau.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文