Вопрос по c++11, tr1, c++, boost – Требует ли от конструктора копирования std :: function типы аргументов типа шаблона быть полными типами?

6

Дано:

#include <functional>
class world_building_gun;
class tile_bounding_box;
typedef std::function<void (world_building_gun, tile_bounding_box)> worldgen_function_t;
void foo() {
    worldgen_function_t v;
    worldgen_function_t w(v);
}

Это должно скомпилироваться? Мои компиляторы говорят:

Да: GCC / stdlibc ++ (также boost :: function есть как в GCC, так и в Clang)

№: Clang / libc ++ (http://libcxx.llvm.org/ , Clang 3.0, libc ++ SVN на сегодняшний день)

(Если правильный ответ «нет», я исправлю свой реальный код, чтобы полные типы помещались в большее количество заголовков или использовался boost :: function.)

РЕДАКТИРОВАТЬ: Вот сообщение об ошибке Clang:

In file included from foo.cpp:2:
In file included from /usr/include/c++/v1/functional:462:
/usr/include/c++/v1/type_traits:2766:19: error: invalid appli
    static_assert(sizeof(_Tp) > 0, "Type must be complete.");
                  ^~~~~~~~~~~
/usr/include/c++/v1/type_traits:2752:15: note: in instantiation of template class 'std::__1::__check_complete<world_buildin
    : private __check_complete<_Hp>,
              ^
/usr/include/c++/v1/type_traits:2753:15: note: in instantiation of template class 'std::__1::__check_complete<world_buildin
      private __check_complete<_T0, _Tp...>
              ^
/usr/include/c++/v1/type_traits:2919:15: note: in instantiation of template class 'std::__1::__check_complete<std::__1::fun
      world_building_gun, tile_bounding_box>' requested here
    : private __check_complete<_Fp, _Args...>
              ^
/usr/include/c++/v1/type_traits:2930:11: note: in instantiation of template class 'std::__1::__invokable_imp<std::__1::func
      world_building_gun, tile_bounding_box>' requested here
          __invokable_imp<_Fp, _Args...>::value>
          ^
/usr/include/c++/v1/functional:1115:33: note: in instantiation of template class 'std::__1::__invokable<std::__1::function<
      world_building_gun, tile_bounding_box>' requested here
    template <class _Fp, bool = __invokable<_Fp&, _ArgTypes...>::value>
                                ^
/usr/include/c++/v1/functional:1141:35: note: in instantiation of default argument for '__callable<std::__1::function<void (world_building_gun, tile_bounding_box)> >' required here
              typename enable_if<__callable<_Fp>::value>::type* = 0);
                                  ^~~~~~~~~~~~~~~
/usr/include/c++/v1/functional:1140:7: note: while substituting deduced template arguments into function template 'function' [with _Fp = std::__1::function<void
      (world_building_gun, tile_bounding_box)>]
      function(_Fp,
      ^
foo.cpp:4:7: note: forward declaration of 'world_building_gun'
class world_building_gun;
      ^
In file included from foo.cpp:2:
In file included from /usr/include/c++/v1/functional:462:
/usr/include/c++/v1/type_traits:2766:19: error: invalid application of 'sizeof' to an incomplete type 'tile_bounding_box'
    static_assert(sizeof(_Tp) > 0, "Type must be complete.");
                  ^~~~~~~~~~~
/usr/include/c++/v1/type_traits:2753:15: note: in instantiation of template class 'std::__1::__check_complete<tile_bounding_box>' requested here
      private __check_complete<_T0, _Tp...>
              ^
/usr/include/c++/v1/type_traits:2753:15: note: in instantiation of template class 'std::__1::__check_complete<world_building_gun, tile_bounding_box>' requested here
      private __check_complete<_T0, _Tp...>
              ^
/usr/include/c++/v1/type_traits:2919:15: note: in instantiation of template class 'std::__1::__check_complete<std::__1::function<void (world_building_gun, tile_bounding_box)> &,
      world_building_gun, tile_bounding_box>' requested here
    : private __check_complete<_Fp, _Args...>
              ^
/usr/include/c++/v1/type_traits:2930:11: note: in instantiation of template class 'std::__1::__invokable_imp<std::__1::function<void (world_building_gun, tile_bounding_box)> &,
      world_building_gun, tile_bounding_box>' requested here
          __invokable_imp<_Fp, _Args...>::value>
          ^
/usr/include/c++/v1/functional:1115:33: note: in instantiation of template class 'std::__1::__invokable<std::__1::function<void (world_building_gun, tile_bounding_box)> &,
      world_building_gun, tile_bounding_box>' requested here
    template <class _Fp, bool = __invokable<_Fp&, _ArgTypes...>::value>
                                ^
/usr/include/c++/v1/functional:1141:35: note: in instantiation of default argument for '__callable<std::__1::function<void (world_building_gun, tile_bounding_box)> >' required here
              typename enable_if<__callable<_Fp>::value>::type* = 0);
                                  ^~~~~~~~~~~~~~~
/usr/include/c++/v1/functional:1140:7: note: while substituting deduced template arguments into function template 'function' [with _Fp = std::__1::function<void
      (world_building_gun, tile_bounding_box)>]
      function(_Fp,
      ^
foo.cpp:5:7: note: forward declaration of 'tile_bounding_box'
class tile_bounding_box;
      ^
2 errors generated.

Clang + libc ++ успешно компилируется, если я удаляю строку «worldgen_function_t w (v);» или если я сделаю классы завершенными типами.

Clang говорит только «Нет»? Бьюсь об заклад, это обеспечивает какой-то вывод ошибок. Не могли бы вы опубликовать это? mfontanini
Можете ли вы попробовать Clang с libstdc ++? Это может быть побочным эффектом проверок SFINAE в libc ++, которые пытаются выяснить, действительно ли то, что передано в его шаблонный ctor, действительно вызывается с типами аргументов в параметре шаблона. Это все неоцененный контекст, но он делает такие вещи, какdecltype(declval<F>()(declval<Args>()...)), что может вызвать эту проблему. Из реализаций POV я не вижу причин для того, чтобы этот код не компилировался. Xeo
Я не могу сейчас попробовать Clang с libstdc ++ из-заbugs.archlinux.org/task/29397 , но Clang + Boost.Function (#include & lt; boost / function.hpp & gt ;, используйте boost :: function) успешно компилирует это (поэтому критическим фактором здесь, вероятно, является библиотека, а не компилятор). idupree
Вы, компиляторы, говорите, что должно компилироваться? Crazy Eddie
@ mfontanini, слава богу, компиляторы просто не говорят "нет" Trevor Hickey

Ваш Ответ

3   ответа
4

Edit: Apperently, эта проблема теперь исправлена, поэтому приведенный ниже текст можно рассматривать как историю. :)

Проблема действительно (как я и предсказывал) с проверками SFINAE libc ++ в шаблонном ctor (для обоснования проверьтеэтот вопрос). Он проверяет, допустимо ли следующее (например), и выдает красивую и чистую ошибку вconstruction сайт, а не глубоко внутри кишкиstd::function (попробуйте следующий пример сlibstd ++ или MSVC ...shudder):

#include <functional>

void f(int* p){}

int main(){
  std::function<void(int)> fun(f);
}

libc ++ приведет к тому, что компилятор выложит что-то вроде строк "не найдено конструктора, соответствующего списку аргументов"void (*)(int*)& quot ;, поскольку единственный применимый (шаблонный ctor) выводит SFINAE 'd.

Тем не менее, так что__callable а также__invoke_imp проверяет работу, аргументы и возвращаемые типы должны быть завершены, поскольку в противном случае неявные преобразования здесь не будут приниматься во внимание.

Причина того, что шаблонный ctor даже просматривается, состоит в том, что все ctors перечисляются перед рассмотрением наилучшего соответствия (в данном случае ctor копии).

Теперь в стандарте очень ясно, что аргументы и возвращаемые типы должны быть завершены при построенииstd::function объект из вызываемого объекта (он же вызывает шаблонный ctor):

§20.8.11.2.1 [func.wrap.func.con] p7

template <class F> function(F f);
template <class F, class A> function(allocator_arg_t, const A& a, F f);

Requires: F shall be CopyConstructible. f shall be Callable (20.8.11.2) for argument types ArgTypes and return type R. [...]

(Примечание: "Требуется" относится кuser функциональности, а не исполнителя.)

§20.8.11.2 [func.wrap.func] p2

A callable object f of type F is Callable for argument types ArgTypes and return type R if the expression INVOKE(f, declval<ArgTypes>()..., R), considered as an unevaluated operand (Clause 5), is well formed (20.8.2).

§20.8.2 [func.req]

p1 Define INVOKE(f, t1, t2, ..., tN) as follows:

(t1.*f)(t2, ..., tN) when f is a pointer to a member function of a class T and t1 is an object of type T or a reference to an object of type T or a reference to an object of a type derived from T; ((*t1).*f)(t2, ..., tN) when f is a pointer to a member function of a class T and t1 is not one of the types described in the previous item; [...] f(t1, t2, ..., tN) in all other cases.

p2 Define INVOKE(f, t1, t2, ..., tN, R) as INVOKE(f, t1, t2, ..., tN) implicitly converted to R.

Таким образом, libc ++, безусловно, имеет право выполнять проверку SFINAE в шаблонном ctor, поскольку типы должны быть полными, так как в противном случае вы получите неопределенное поведение. Тем не менее, это может быть немного неудачно и считается дефектом, что проверка безопасности для полного типа запускается, даже если фактическая проверка SFINAE никогда не нужна (потому что всегда будет вызываться копия ctor). Это можно облегчить, сделавcallable отметьте ленивый, как

template<bool Copy, class F>
struct lazy_callable{
  static bool const value = callable<F>::value;
};

template<class F>
struct lazy_callable<true, F>{
  static bool const value = false;
};

template<class F>
function(F f, typename enable_if<lazy_callable<!std::is_same<F,function>::value>::type* = 0);

Это должно только вызватьcallable SFINAE проверить, еслиF на самом деле неstd::function<...>.

Человек яmay немного отвлекся здесь в конце ...

Я не делал ничего особенного, чтобы справиться с этим, так что может быть разница во внешних интерфейсах компилятора
Я не думаю, что речь идет о лени. посколькуF является параметром шаблона конструктора, вызываемая проверка должна быть зависимой и не приводить к серьезной ошибке, когдаstd::function<Sig> создается экземпляр. Может ли это быть ограничением Clang, когда речь идет о SFINAE, а не дефектом в libc ++?
@JonathanWakely: интересно. Как вы решаете проблему, заключающуюся в том, что аргументы и возвращаемые типы должны быть завершены во время проверки? Я поболтал с Люком Дантоном и пришел к выводу, что стандарт оставляет егоunspecified будет ли создан экземпляр шаблона во время разрешения перегрузки, если он не был вызван в конце (что будет иметь место здесь). Или это различие между разрешением перегрузки GCC и Clang?
Нотабене Я изменил libstdc ++, чтобы отключить конструктор, когда F не вызывается (что является предлагаемым разрешением дляLWG 2132) так G ++ 4.8 скажетno matching function for call to 'std::function<void(int)>::function(void (&)(int*))' для вашего примера, но все еще может скомпилировать пример OP без ошибок
4

так что этот пример теперь компилируется, зафиксировал ревизию 160285.

Я полагаю, что libc ++ был слишком агрессивен при проверке списка аргументов для полных типов.

Не забудьте прокомментировать мой ответ w.r.t. проверка полноты? Такая «ленивая проверка» не должна помогать и все-таки разрешать проверку полноты, чтобы пользователь не получал неопределенного поведения (если я правильно понял часть «Обязательные», то есть).
Я думаю, что отключение этого конструктора как конструктора копирования (и то же самое для назначения), как подсказывает ваш ответ, является хорошей идеей. Я только что проверил код в libc ++, чтобы сделать это. Спасибо!
3

.func], мы имеем:

3 The function class template is a call wrapper (20.8.1) whose call signature (20.8.1) is R(ArgTypes...).

В 20.8.1 Definitions [func.def] мы получаем следующие определения того, что составляет тип оболочки вызова, оболочку вызова и подпись вызова:

2 A call signature is the name of a return type followed by a parenthesized comma-separated list of zero or more argument types.

5 A call wrapper type is a type that holds a callable object and supports a call operation that forwards to that object.

6 A call wrapper is an object of a call wrapper type.

Обратите внимание, что в параграфе 2 не упоминается полнота типов.

Короче говоря, (используется много определений) значение термина «вызываемый объект»; здесь означает либо функтор (знакомое понятие, то есть нечто, что можно использовать как функцию), либо указатель на член. Кроме того, стандарт также описываетCallable концепция в пункте 20.8.11.2:

A callable object f of type F is Callable for argument types ArgTypes and return type R if the expression INVOKE(f, declval<ArgTypes>()..., R), considered as an unevaluated operand (Clause 5), is well formed (20.8.2).

(TheINVOKE бит - это воображаемая функция, которую стандарт использует для определения того, как функторы и указатели на члены вызываются.)

То, что я думаю, является самым важным выводом из этого является следующее требование:

given a callable object that is Callable with signature R(A...), then R and A... are complete (or possibly R is void) by virtue of the INVOKE expression (i.e. otherwise it would not be well-formed, notice the use of declval<ArgTypes>()...)

Мой аргумент теперь основан на «операции вызова [], которая перенаправляет на этот объект» в определении обертки вызовов, которое, я думаю, намеренно оставлено расплывчатым, чтобы не быть чрезмерно ограничительным. В случаеstd::function<Sig> где некоторые неполные типы участвуют вSig затем мы могли бы определить эту операцию как «сначала завершить типы,then лечитьstd::function<Sig> как вызываемый объект типа подписи вызоваSig& APOS ;.

Учитывая это, вот ключевые моменты моего аргумента:

std::function is not described as being a callable object or as being Callable for any signature invoking an std::function is done in terms of INVOKE (20.8.11.2.4 function invocation [func.wrap.func.inv]) constructing an std::function from a callable object is in terms of Callable with the call signature of std::function (20.8.11.2.1 function construct/copy/destroy [func.wrap.func.con] paragraph 7) calling the target member of std::function is in terms of Callable with the call signature of std::function (20.8.11.2.5 function target access [func.wrap.func.targ]) all other operations of std::function are not described in terms of callable object(*) , Callable, INVOKE or otherwise require that the call signature of std::function involve complete types

(*) за исключением одного конструктора, в котором описание содержит & quot; не выдает исключений, еслиf& s; x2019; s цель - вызываемый объект, переданный черезreference_wrapper или указатель на функцию & quot ;. Я думаю, что в контексте ясно, что это не влияет на аргумент. Что бы это ни стоило, этот конструктор не участвует в фрагменте OP.

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

Хорошо и хорошо анализировать, что предписывает Стандарт, но также важно учитывать, чтоintent стандарта. В этом случае я думаю, что это очень желательно и ожидается, чтоstd::function не требует, чтобы типы подписи вызова были полными. Учтите следующее:

// in a_fwd.hpp
struct incomplete;
using callback_type = std::function<void(incomplete)>;
callback_type make_callback();

// in b.hpp; depends on a_fwd.hpp
#include "a_fwd.hpp"
void eat_callback(callback_type);

Тогда без требования не связанный TU, назовем его C, то есть клиент B, может сделать:

// in c.cpp
#include "b.hpp"

// somewhere in e.g. a function body
eat_callback(make_callback());

Это безопасно для типов и сводит к минимуму сцепление, так как только блок перевода B должен знать о деталях блока перевода A.

Кроме того, Boost.Function и libstdc ++ продемонстрировали, что можно реализоватьstd::function без такого требования.

Похожие вопросы