Re: Условный оператор, чай баг - мой или gcc ?
От: FrozenHeart  
Дата: 30.05.13 19:55
Оценка:
Было бы неплохо, если бы Вы снабжали свои сообщения ссылками на какой-нибудь онлайн-компилятор — не у всех под рукой имеется обычная версия. Т.к. на 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 приводится к типу указателя на функцию. В связи с этим, исходный пример можно переписать следующим образом:

#include <functional>
#include <iostream>

int main()
{
    typedef void (*fp_t)();

    fp_t fp = nullptr;
    std::function<void()> f = fp;
    std::cout << (f == nullptr) << '\n';
}


Как и в случае с исходным примером, данный код также выведет "0" в gcc 4.7.2 и MSVC-11.0.

Посмотрим, что из себя представляет operator== для данных типов в стандарте:

ISO/IEC 14882:2011

20.8.11.2.6 null pointer comparison operators [func.wrap.func.nullptr]

template <class R, class... ArgTypes>
bool operator==(const function<R(ArgTypes...)>& f, nullptr_t) noexcept;
template <class R, class... ArgTypes>
bool operator==(nullptr_t, const function<R(ArgTypes...)>& f) noexcept;
1 Returns: !f.


Теперь посмотрим, как мы конструировали объект класса 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:

template<typename _Signature>
  static bool
  _M_not_empty_function(const function<_Signature>& __f)
  { return static_cast<bool>(__f); }

template<typename _Tp>
  static bool
  _M_not_empty_function(const _Tp*& __fp)
  { return __fp; }

template<typename _Class, typename _Tp>
  static bool
  _M_not_empty_function(_Tp _Class::* const& __mp)
  { return __mp; }

template<typename _Tp>
  static bool
  _M_not_empty_function(const _Tp&)
  { return true; }


Возможно, что предполагалось то, что функция

template<typename _Tp>
  static bool
  _M_not_empty_function(const _Tp*& __fp)
  { return __fp; }


будет вызываться для указателей на функцию, но на деле оказалось не так. В случаях, подобных тому, что приведён в исходном коде темы, будет вызываться перегрузка функции, которая всегда возвращает true:

template<typename _Tp>
  static bool
  _M_not_empty_function(const _Tp&)
  { return true; }


Между прочим, boost::function ведёт себя точно так же в случае сравнения с nullptr:

#include <boost/function.hpp>

#include <iostream>

int main()
{
    typedef void (*fp_t)();

    fp_t fp = nullptr;
    boost::function<void()> f = fp;
    std::cout << (f == nullptr) << '\n';
}


В отличие от 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.


Исходный пример можно переписать, например, вот так:

#include <boost/function.hpp>

#include <iostream>

typedef boost::function<void(int)> fp_t;

void bar(int) {}

void baz(fp_t fp)
{
    if (!fp.empty())
    {
        std::cout << "true \n";
        fp(0);
    }
    else
    {
        std::cout << "false \n";
    }
}

int main()
{
    bool foo = false;
    baz(foo ? &bar : nullptr);
}


Или так:

#include <boost/function.hpp>

#include <iostream>

typedef boost::function<void(int)> fp_t;

void bar(int) {}

void baz(fp_t fp)
{
    if (fp)
    {
        std::cout << "true \n";
        fp(0);
    }
    else
    {
        std::cout << "false \n";
    }
}

int main()
{
    bool foo = false;
    baz(foo ? &bar : nullptr);
}


http://www.boost.org/doc/libs/1_53_0/doc/html/boost/function.html#idp41743568-bb

operator safe_bool() const;

Returns:
A safe_bool that evaluates false in a boolean context when this->empty(), and true otherwise.

Throws:
Will not throw.

 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.