Почему эта двусмысленность здесь? - PullRequest
33 голосов
/ 19 августа 2010

Считайте, что у меня есть следующий минимальный код:

#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[] на следующее приводит к компиляции без ошибок?

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

Ссылки на стандарт C ++ приветствуются.


Как я вижу, здесь есть два пути преобразования:

  1. (1) int до size_t и (2) operator[](size_t).
  2. (1) operator ptr_t&(), (2) int до size_t и (3) встроенный operator[](size_t).

Ответы [ 6 ]

31 голосов
/ 19 августа 2010

Это на самом деле довольно просто.Для t[1] разрешение перегрузки имеет следующие кандидаты:

Кандидат 1 (встроенный: 13,6 / 13) (T - некоторый произвольный тип объекта):

  • Список параметров: (T*, ptrdiff_t)

Кандидат 2 (ваш оператор)

  • Список параметров: (TData<float[100][100]>&, something unsigned)

Список аргументов задается как 13.3.1.2/6:

Набор функций-кандидатов для разрешения перегрузки - это объединение кандидатов-членов, кандидатов, не являющихся членами, и встроенных кандидатов.Список аргументов содержит все операнды оператора.

  • Список аргументов: (TData<float[100][100]>, int)

Вы видите, что первый аргумент соответствует первому параметруКандидат 2 точно.Но для этого требуется определенное пользователем преобразование для первого параметра кандидата 1. Таким образом, для первого параметра побеждает второй кандидат.

Вы также видите, что исход второй позиции зависит.Давайте сделаем некоторые предположения и посмотрим, что мы получим:

  1. ptrdiff_t равно int: выигрывает первый кандидат, поскольку он имеет точное совпадение, в то время как второй кандидат требует интегрального преобразования.
  2. ptrdiff_t равно long: ни один из кандидатов не выигрывает, поскольку оба требуют интегрального преобразования.

Теперь 13.3.3/1 говорит

Пусть ICSi (F) обозначает неявную последовательность преобразования, которая преобразует i-й аргумент в списке в тип i-й параметр жизнеспособной функции F.

Жизнеспособная функция 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 - но вы получаете двусмысленность, потому что вы вкритическая ситуация.

13 голосов
/ 19 августа 2010

с выражением:

t[1][1] = 5;

Компилятор должен сфокусироваться на левой стороне, чтобы определить, что там происходит, поэтому = 5; игнорируется, пока не разрешится lhs. Оставив нам выражение: t[1][1], представляющее две операции, вторая работает с результатом первого, поэтому компилятор должен учитывать только первую часть выражения: t[1]. Фактический тип (TData&)[(int)]

Вызов не соответствует в точности ни одной функции, так как operator[] для TData определен как принимающий аргумент size_t, поэтому чтобы использовать его, компилятор должен преобразовать 1 из int в size_t с неявным преобразованием. Это первый выбор. Теперь еще один возможный путь - применение пользовательского преобразования для преобразования TData<float[100][100]> в float[100][100].

Преобразование int в size_t представляет собой интегральное преобразование и оценивается как Преобразование в Таблице 9 стандарта, как и определяемое пользователем преобразование из TData<float[100][100]> в 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) без каких-либо преобразований, поэтому компилятор должен следовать этому пути.

0 голосов
/ 19 августа 2010

Разрешение перегрузки является головной болью.Но так как вы наткнулись на исправление (исключите преобразование операнда индекса в operator[]), которое слишком специфично для примера (литералы имеют тип int, но большинство переменных, которые вы будете использовать, - нет), возможно, вы можете обобщитьit:

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.

0 голосов
/ 19 августа 2010

Мне кажется, что с

t[1][1] = 5;

компилятор должен выбирать между.

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

, который совпадал бы, если бы литерал int был преобразован в size_t или

operator ptr_t & () { return data; }

с последующим обычным индексированием массива, в этом случае тип индекса точно совпадает.


Что касается ошибки, GCC представляется как расширение компиляторахотел бы выбрать первую перегрузку для вас, и вы компилируете с флагом -pedantic и / или -Werror, который заставляет его придерживаться слова стандарта.

(я не в -Настроение педантичное, поэтому без стандартных цитат, особенно по этой теме.)

0 голосов
/ 19 августа 2010

Не знаю, каков точный ответ, но ...

Из-за этого оператора:

operator ptr_t & () { return data; }

уже существует встроенный оператор [] (подписка на массив) который принимает size_t в качестве индекса.Итак, у нас есть два [] оператора, встроенных и определенных вами.Бут принимает size_t, так что это, вероятно, считается недопустимой перегрузкой.

// РЕДАКТИРОВАТЬэто должно работать так, как вы хотели

template<typename ptr_t>
struct TData
{
    ptr_t data;
    operator ptr_t & () { return data; }
};
0 голосов
/ 19 августа 2010

Я попытался показать двух кандидатов на выражение 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 и должна быть лучше. Но вот подвох ...

Здесь должна помочь приведенная ниже цитата из Standard.

$ 13.3.3.2 / 3 штатов - Стандарт последовательность преобразования S1 лучше последовательность преобразования, чем стандартная последовательность преобразования S2, если - S1 является правильная подпоследовательность S2 (сравнение последовательности преобразования в каноническая форма, определенная 13.3.3.1.1, исключая любое преобразование Lvalue; последовательность преобразования идентичности считается подпоследовательностью любого последовательность преобразования неидентификации) или, если не то ...

$ 13.3.3.2 состояния- "Определяется пользователем последовательность преобразования U1 лучше последовательность преобразования, чем другой определяемая пользователем последовательность преобразования U2, если они содержат одинаковые пользовательские функция преобразования или конструктор и если второе стандартное преобразование последовательность U1 лучше, чем вторая стандартная последовательность преобразования U2. "

Здесь первая часть условия and ", если они содержат одинаковую пользовательскую функцию преобразования или конструктор ", не выполняется. Таким образом, даже если вторая часть и условия ", если вторая стандартная последовательность преобразования U1 лучше, чем вторая стандартная последовательность преобразования U2. ", остается в силе, ни S1, ни S2 не предпочтительнее других .

Вот почему сообщение об ошибке фантом от gcc "ISO C ++ говорит, что они неоднозначны, хотя худшее преобразование для первого лучше, чем худшее преобразование для второго"

Это объясняет тихую двусмысленность ИМХО

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...