Всем привет.
Столкнулся с таким загадочным поведением gcc.
Вообщем есть такая программка, скомпиленая на gcc 4.7.2 с поддержкой 11 стандарта и некоторыми его фичами (std::function).
Если следовать логике, то в тирнарном операторе должен вычислятся правый операнд (nullptr) и он же должен передаваться в call(),
где значение nullptr используется для инициализации fPtr. По факту — в fPtr попадаетя какой то мусор, который совсем не равен nullptr.
Да, и еще — gcc, при компиляции, не выдает никаких ошибок или ворнингов.
В итоге починилоь все таким образом —
24 call((setCall ? ffPtrT(&f1) : ffPtrT(nullptr)));
// на самом деле все работает даже если только для 2го операнда создавать временный указатель
// то есть
24 call((setCall ? ffPtrT(&f1) : nullptr));
Пожалуйста, переформатируйте код, глаза на лоб лезут читать этот дамп
SZ>А теперь вопрос знатокам С++ — что это было ?
Наугад: скорее всего, обычный для тернарного оператора подводный камень — оба операнда должны иметь одинаковый тип (или приводиться к нему), иначе вывод типа попытается сделать компилятор, а далее уж как повезёт, обычная рулетка type casting.
Здравствуйте, Mr.Delphist, Вы писали:
MD>Здравствуйте, ScorpZ, Вы писали:
MD>Пожалуйста, переформатируйте код, глаза на лоб лезут читать этот дамп
SZ>>А теперь вопрос знатокам С++ — что это было ?
MD>Наугад: скорее всего, обычный для тернарного оператора подводный камень — оба операнда должны иметь одинаковый тип (или приводиться к нему), иначе вывод типа попытается сделать компилятор, а далее уж как повезёт, обычная рулетка type casting.
Я тоже думал в этом направлении.
На мсдн (кстати не знаю как этот код будет компилиться и работать на VC++), по поводу условного оператора, написано следующее —
"If both expressions are of pointer types or if one is a pointer type and the other is a constant expression that evaluates to 0, pointer conversions are performed to convert them to a common type."
Получается, что gcc пытается nullptr привести к указателю на ф-цию, а получается у него это не очень.
Здравствуйте, Mr.Delphist, Вы писали:
MD>Здравствуйте, ScorpZ, Вы писали:
MD>Пожалуйста, переформатируйте код, глаза на лоб лезут читать этот дамп
SZ>>А теперь вопрос знатокам С++ — что это было ?
MD>Наугад: скорее всего, обычный для тернарного оператора подводный камень — оба операнда должны иметь одинаковый тип (или приводиться к нему), иначе вывод типа попытается сделать компилятор, а далее уж как повезёт, обычная рулетка type casting.
Сорри, не нашел где редактировать предидущее сообщение.
И еще заметка по теме, если вот этот модерный тип — std::function поменять на простой указатель на ф-цию, то все прекрасно работает.
Здравствуйте, ScorpZ, Вы писали:
SZ>Сорри, не нашел где редактировать предидущее сообщение.
Ну, можно ответом самому себе, только с тегом ccode и без номеров строк
SZ>И еще заметка по теме, если вот этот модерный тип — std::function поменять на простой указатель на ф-цию, то все прекрасно работает.
Ну так std::function — это класс с оператором "скобочки", а попытка превратить адрес экзмпляра этого класса в указатель на свободную функцию громко упадёт.
Здравствуйте, ScorpZ, Вы писали:
SZ> Всем привет. SZ>Столкнулся с таким загадочным поведением gcc. SZ>Вообщем есть такая программка, скомпиленая на gcc 4.7.2 с поддержкой 11 стандарта и некоторыми его фичами (std::function). SZ>Если следовать логике, то в тирнарном операторе должен вычислятся правый операнд (nullptr) и он же должен передаваться в call(), SZ>где значение nullptr используется для инициализации fPtr. По факту — в fPtr попадаетя какой то мусор, который совсем не равен nullptr.
SZ> Да, и еще — gcc, при компиляции, не выдает никаких ошибок или ворнингов.
это бага gcc, твой код (+ реализация std::function из gcc) можно свести к такому:
При этом, очевидно, будет вызвана перегрузка _M_not_empty_function, которая всегда возвращает 'true', поскольку аналогичный код используется std::function, созданный объект будет считать, что у него есть ненулевой указатель на функцию.
SZ>В итоге починилоь все таким образом —
SZ>24 call((setCall ? ffPtrT(&f1) : ffPtrT(nullptr))); SZ>// на самом деле все работает даже если только для 2го операнда создавать временный указатель SZ>// то есть
SZ>24 call((setCall ? ffPtrT(&f1) : nullptr));
А это работает правильно, т.к. у std::function (по стандарту) есть специальный конструктор принимающий nullptr_t, который, естественно, никаких проверок не делает, а сразу инициализирует пустой функтор.
— Как известно, conditional operator требует, чтобы переданные ему второй и третий операнды были одного и того же типа. В случае несовпадения типов он будет пытаться преобразовать один из них к другому. В данном случае он, видимо, решает преобразовать nullptr к указателю на функцию (не уверен, не могу найти точного доказательства в стандарте)
— Если не ошибаюсь, далее мы имеем примерно следующую ситуацию:
Здравствуйте, Nikita.Trophimov, Вы писали: NT>Попробую собрать мысли воедино NT>Получается, что: NT>- Как известно, conditional operator требует, чтобы переданные ему второй и третий операнды были одного и того же типа. В случае несовпадения типов он будет пытаться преобразовать один из них к другому. В данном случае он, видимо, решает преобразовать nullptr к указателю на функцию (не уверен, не могу найти точного доказательства в стандарте) NT>- Если не ошибаюсь, далее мы имеем примерно следующую ситуацию:
Да, тут все верно. NT>Данный код выведет в stdout "false" (также не уверен, почему именно) NT>Что происходит? Кто-нибудь может помочь, пожалуйста?
Так я же, вроде, объяснил. std::function проверят переданный указатель на функция с помощью набора перегруженных методов '_M_not_empty_function'. Один из них принимает ссылку на std::function, другой — указатель на функцию-член класса, эти перегрузки, очевидно не подходят, остаются еще 2, которые я приводил. Из этих перегрузок выбирается та, которая всегда возвращает 'true', поэтому std::function считает, что передан ненулевой указатель.
Здравствуйте, Nikita.Trophimov, Вы писали:
NT>Попробую собрать мысли воедино
NT>Получается, что:
NT>- Как известно, conditional operator требует, чтобы переданные ему второй и третий операнды были одного и того же типа. В случае несовпадения типов он будет пытаться преобразовать один из них к другому. В данном случае он, видимо, решает преобразовать nullptr к указателю на функцию (не уверен, не могу найти точного доказательства в стандарте) NT>- Если не ошибаюсь, далее мы имеем примерно следующую ситуацию:
NT>
NT>Данный код выведет в stdout "false" (также не уверен, почему именно)
NT>Что происходит? Кто-нибудь может помочь, пожалуйста?
Да , вот фраза из MSDN —
If both expressions are of pointer types or if one is a pointer type and the other is a constant expression that evaluates to 0, pointer conversions are performed to convert them to a common type.
получается , что для выражения —
// (setCall ? &f1 : nullptr)
"common type" как раз и будет указатель на ф-цию. То есть при преобразовании значения типа (std::nullptr_t)
в (void(*)(int)) где то и происходит сбой.
Поскольку в тернарном операторе (cond ? &f1 : nullptr) универсальный std::nullptr_t конкретизируется до типизированного FUNPTR, — получаем то, что получаем.
Вместо ffPtrT(nullptr) в ветке тернарного оператора можно написать просто ffPtrT().
Здравствуйте, Кодт, Вы писали:
К>Вместо ffPtrT(nullptr) в ветке тернарного оператора можно написать просто ffPtrT().
Та ну — ffPtrT(nullptr) как то нагляднее,
... А теперь самое интересное, запустил я свой пример на VC++ 2010 , все работает правильно и замечательно !!!
Таки gcc что то намудрили.
Было бы неплохо, если бы Вы снабжали свои сообщения ссылками на какой-нибудь онлайн-компилятор — не у всех под рукой имеется обычная версия. Т.к. на LWS до сих пор нельзя выкладывать новые куски кода, сделаем это на ideone — тут.
Насколько я понял, происходит вот что.
Как уже было неоднократно замечено, в случае несовпадения типов второго и третьего операндов conditional operator'а, компилятор попытается привести один из них к другому согласно тем правилам, что указаны в 5.16 Conditional operator [expr.cond]. Если быть более конкретным, случай, описанный в теме, относится к следующему (выделял самостоятельно):
ISO/IEC 14882:2011
5.16 Conditional operator [expr.cond]
6 Lvalue-to-rvalue (4.1), array-to-pointer (4.2), and function-to-pointer (4.3) standard conversions are performed
on the second and third operands. After those conversions, one of the following shall hold:
[...]
— The second and third operands have pointer type, or one has pointer type and the other is a null
pointer constant, or both are null pointer constants, at least one of which is non-integral; pointer
conversions (4.10) and qualification conversions (4.4) are performed to bring them to their composite
pointer type (5.9). The result is of the composite pointer type.
Посмотрим, что из себя представляет composite type, и как он получается в данном случае (выделял самостоятельно):
ISO/IEC 14882:2011
5.9 Relational operators [expr.rel]
2 [...] If one operand is a null pointer constant, the composite
pointer type is std::nullptr_t if the other operand is also a null pointer constant or, if the other operand
is a pointer, the type of the other operand.
Значит, nullptr приводится к типу указателя на функцию. В связи с этим, исходный пример можно переписать следующим образом:
Теперь посмотрим, как мы конструировали объект класса std::function (выделял самостоятельно):
ISO/IEC 14882:2011
20.8.11.2.1 function construct/copy/destroy [func.wrap.func.con]
template<class F> function(F f);
template <class F, class A> function(allocator_arg_t, const A& a, F f);
[...]
8 Postconditions: !*this if any of the following hold:
— f is a NULL function pointer.
— f is a NULL pointer to member.
— F is an instance of the function class template, and !f
Т.к. fp из примера выше является null function pointer, то, согласно этой цитате, гарантируется, что в результате конструирования объекта выражение !f, которое как раз и используется для сравнения с nullptr при помощи operator==, будет равняться true.
На основе этих данных можно полагать, что это баг. Воспроизводится как минимум на gcc 4.7.2 и MSVC-11.0. Работает так, как ожидается (т.е. выводит "1"), в MSVC-10.0.
По этому поводу уже был открыт соответствующий баг-репорт.
Разумеется, это уже детали реализации, но если кому интересно, что происходит в случае gcc, попробуем посмотреть.
Внутри мы имеем следующие перегруженные версии функции _M_not_empty_function:
будет вызываться для указателей на функцию, но на деле оказалось не так. В случаях, подобных тому, что приведён в исходном коде темы, будет вызываться перегрузка функции, которая всегда возвращает true:
В отличие от std::function, в документации к конструктору boost::function не говорится явно, что postcondition'ом в случае null pointer function является !*this. Вместо этого они гарантируют, что функция-член empty будет возвращать true.
template<typename F> function(F f);
Requires:
F is a function object Callable from this.
Postconditions:
*this targets a copy of f if f is nonempty, or this->empty() if f is empty.
Исходный пример можно переписать, например, вот так: