Должно ли назначение std::function игнорировать возвращаемый тип?

Является ли приведенный ниже код действительным C++ в соответствии со стандартом C++11 или C++14?

#include <functional>

int ReturnInt()
{
  return 5;
}

int main( int argc, char **argv )
{
  std::function< void () > BoundType = ReturnInt;
  return 0;
}

Код отлично компилируется с последними версиями cygwin gcc (4.8.3) и clang (4.3.2), но не с Visual Studio 2013, CTP Visual Studio за ноябрь 2013 г. или предварительной версией Visual Studio 14. Он также компилируется на всех платформах, если std::function изменен на boost::function.

Я нашел этот другой вопрос о переполнении стека, который предполагает, что он должен работать.


person fun4jimmy    schedule 19.08.2014    source источник
comment
Да, похоже, это та же проблема. Я пытался найти его перед публикацией, но, очевидно, недостаточно усердно, извините.   -  person fun4jimmy    schedule 19.08.2014


Ответы (2)


Код имеет неопределенное поведение в C++11 и неправильный формат в C++14. C++14 добавляет это Remark в спецификацию этого конструктора:

Примечания. Эти конструкторы не должны участвовать в разрешении перегрузки, если только f не является Callable (20.9.11.2) для типов аргументов ArgTypes... и возвращаемого типа R.

Callable определен в [func.wrap.func]/p2:

Вызываемый объект f типа F является Вызываемым для типов аргументов ArgTypes и возвращаемого типа R, если выражение INVOKE (f, declval<ArgTypes>()..., R), рассматриваемое как невычисленный операнд (пункт 5), правильно сформировано. (20.9.2).

Чтобы этот INVOKE был правильно сформирован, возвращаемый тип INVOKE без R должен быть неявно преобразован в R ([func.require]/p2).

В C++11 эти операторы находились в предложении Requries, что означает, что клиент должен сделать их правильно, и если клиент не сработает, может произойти что угодно, включая успешную компиляцию.

Это было изменено LWG 2132.

person Howard Hinnant    schedule 19.08.2014
comment
@BЈовић: Ссылка находится прямо в конце моего ответа (возможно, вам придется обновить). ЛВГ 2132. - person Howard Hinnant; 19.08.2014
comment
Насколько я понимаю, это недостаток стандарта. - person Yakk - Adam Nevraumont; 20.08.2014
comment
@Yakk: cplusplus.github.io/LWG/lwg-active.html#submit_issue - person Howard Hinnant; 20.08.2014
comment
это хуже, чем я думал (и кто-то уже представил ошибку для худшей проблемы, которая может решить эту) - person Yakk - Adam Nevraumont; 22.08.2014

std::function в С++ 11 и 14 не имеет желаемого поведения.

Также SFINAE не может обнаружить плохие перегрузки.

Мы можем обернуть его в другой тип, который имеет желаемое поведение (void отбрасывание возврата) и имеет обнаружение плохой перегрузки SFINAE, пока мы на нем:

template<class Sig>
struct checked_function;

template<class R, class... Args>
struct checked_function<R(Args...)>:std::function<R(Args...)> {
  using function = std::function<R(Args...)>;
  checked_function(std::nullptr_t):function() {}
  checked_function():function() {}
  template<class F, class=typename std::enable_if<
    std::is_convertible<
      typename std::result_of< F(Args...) >::type
      , R
    >::value
  >::type>
  checked_function( F&& f ):function( std::forward<F>(f) ) {}

  template<class F, class=typename std::enable_if<
    std::is_convertible<
      typename std::result_of< F(Args...) >::type
      , R
    >::value
  >::type>
  checked_function& operator=( F&& f ) { return function::operator=( std::forward<F>(f) ); }

  checked_function& operator=( checked_function const& o ) = default;
  checked_function& operator=( checked_function && o ) = default;
  checked_function( checked_function const& o ) = default;
  checked_function( checked_function && o ) = default;
};

template<class... Args>
struct checked_function<void(Args...)>:std::function<void(Args...)> {
  using function = std::function<void(Args...)>;
  checked_function(std::nullptr_t):function() {}
  checked_function():function() {}
  template<class F, class=typename std::enable_if<
    std::is_same<
      typename std::result_of< F(Args...) >::type
      , void
    >::value
  >::type>
  checked_function( F&& f, int*unused=nullptr ):function( std::forward<F>(f) ) {}

  template<class F>
  static auto wrap(F&& f){
    return [f_=std::forward<F>(f)](auto&&...args){
      f_( std::forward<decltype(args)>(args)... );
    };
  }
  template<class F, class=typename std::enable_if<
    !std::is_same<
      typename std::result_of< F(Args...) >::type
      , void
    >::value
  >::type>
  checked_function( F&& f, void*unused=nullptr ):
    function( wrap(std::forward<F>(f)) ) {}

   template<class F>
  typename std::enable_if<
    !std::is_same<
      typename std::result_of< F(Args...) >::type
      , void
    >::value,
    checked_function&
  >::type operator=( F&& f ) { return function::operator=( wrap(std::forward<F>(f)) ); }

  template<class F>
  typename std::enable_if<
    std::is_same<
      typename std::result_of< F(Args...) >::type
      , void
    >::value,
    checked_function&
  >::type operator=( F&& f ) { return function::operator=( std::forward<F>(f) ); }

  checked_function& operator=( checked_function const& o ) = default;
  checked_function& operator=( checked_function && o ) = default;
  checked_function( checked_function const& o ) = default;
  checked_function( checked_function && o ) = default;
};

Теперь он компилируется в C++14 (не в C++11, из-за wrap: wrap можно заменить в точке вызова копией собственного тела, так что...). Вероятно, можно было бы уменьшить шаблон на кучу.

Он использует некоторые функции С++ 14 (точнее, перемещение в лямбда-выражение в wrap - вы можете избавиться от этого, добавив больше шаблонов).

Еще не бегал.

person Yakk - Adam Nevraumont    schedule 19.08.2014
comment
Вы ожидаете, что это будет работать так checked_function< void() > BoundType = &ReturnInt;? Я исправил несколько опечаток и прочее, но pastebin.com/VdPqZCzx не компилируется. Я думаю, что std::is_same в специализации void неверен, также вам нужно написать перегрузку operator(), которая фактически игнорирует возвращаемый тип. - person fun4jimmy; 20.08.2014
comment
@ fun4jimmy Вы удалили конструктор, который wrap не возвращает функции, void поэтому, естественно, он не сможет их wrap . Я отредактировал пост с тем, который компилируется. - person Yakk - Adam Nevraumont; 20.08.2014
comment
Ах, извините, я думал, что вы пропустили раздел копирования и вставки, мой плохой. Это работает, огромное спасибо. - person fun4jimmy; 21.08.2014