Универсальная ссылка в конструкторе вызывает сбой, не может назначить вызываемый функтор std :: function

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

Однако, если я использую универсальный эталонный ctor, он не скомпилируется (я включил сообщение об ошибке clang ниже).

Как я могу это исправить или что я делаю не так?

#include <functional>
#include <utility>
#include <exception>

template<typename F>
struct py_catch {
F func;
/*
//Works
py_catch(F f)
:   func ( std::move(f) )
{   } */
//Doesn't
template<typename F2>
py_catch(F2&& f)
:   func ( std::forward<F2>(f) )
{   }

py_catch(py_catch&&)=default;
py_catch(const py_catch&)=default;
py_catch& operator=(const py_catch&)=default;
py_catch& operator=(py_catch&&)=default;

template<typename... Args>
auto operator()(Args&&... args)
-> decltype(func(std::forward<Args>(args)...)) {
try {
return func(std::forward<Args>(args)...);
}
catch(const std::exception&) {
throw;
}
}
};

template<typename F>
py_catch<typename std::remove_reference<F>::type> make_py_catch(F&& f) {
return py_catch<typename std::remove_reference<F>::type>(std::forward<F>(f));
}

int main() {
std::function<void()> s;
s = make_py_catch([]{});
}

Ошибка компиляции:

testcase2.cpp:16:7: error: no matching constructor for initialization of '<lambda at testcase2.cpp:43:23>'
:   func ( std::forward<F2>(f) )
^      ~~~~~~~~~~~~~~~~~~~
/usr/lib/gcc/x86_64-unknown-linux-gnu/4.7.2/../../../../include/c++/4.7.2/functional:1764:10: note: in instantiation of function template specialization 'py_catch<<lambda at testcase2.cpp:43:23> >::py_catch<py_catch<<lambda at testcase2.cpp:43:23> > &>' requested here
new _Functor(*__source._M_access<_Functor*>());
^
/usr/lib/gcc/x86_64-unknown-linux-gnu/4.7.2/../../../../include/c++/4.7.2/functional:1799:8: note: in instantiation of member function 'std::_Function_base::_Base_manager<py_catch<<lambda at testcase2.cpp:43:23> > >::_M_clone' requested here
_M_clone(__dest, __source, _Local_storage());
^
/usr/lib/gcc/x86_64-unknown-linux-gnu/4.7.2/../../../../include/c++/4.7.2/functional:2298:33: note: in instantiation of member function 'std::_Function_base::_Base_manager<py_catch<<lambda at testcase2.cpp:43:23> > >::_M_manager' requested here
_M_manager = &_My_handler::_M_manager;
^
/usr/lib/gcc/x86_64-unknown-linux-gnu/4.7.2/../../../../include/c++/4.7.2/functional:2173:4: note: in instantiation of function template specialization 'std::function<void ()>::function<py_catch<<lambda at testcase2.cpp:43:23> > >' requested here
function(std::forward<_Functor>(__f)).swap(*this);
^
testcase2.cpp:43:7: note: in instantiation of function template specialization 'std::function<void ()>::operator=<py_catch<<lambda at testcase2.cpp:43:23> > >' requested here
s = make_py_catch([]{});
^
testcase2.cpp:43:23: note: candidate constructor (the implicit copy constructor) not viable: no known conversion from 'py_catch<<lambda at testcase2.cpp:43:23> >' to 'const <lambda at testcase2.cpp:43:23>' for 1st argument
s = make_py_catch([]{});
^
testcase2.cpp:43:23: note: candidate constructor (the implicit move constructor) not viable: no known conversion from 'py_catch<<lambda at testcase2.cpp:43:23> >' to '<lambda at testcase2.cpp:43:23>' for 1st argument
s = make_py_catch([]{});
^
testcase2.cpp:43:23: note: candidate constructor (the implicit default constructor) not viable: requires 0 arguments, but 1 was provided
1 error generated.

2

Решение

Я думаю, что проблема заключается в вашем шаблоне конструктора преобразования template<typename F2> py_catch(F2&&) слишком жадный Другой способ вызвать ошибку:

int main() {
auto x( make_py_catch([]{}) );
auto y(x);
}

Эта конструкция копирования использует lvalue некоторого типа py_catch<..>, Copy-ctor ожидает py_catch<..> const&тогда как ваш жадный шаблон обеспечивает перегрузку параметром типа py_catch<..>&, Специальное правило в [over.ics.rank] / 3 теперь гласит, что перегрузка, принимающая ссылку на менее квалифицированный тип, является предпочтительной. Поэтому вызывается не copy-ctor, а шаблон конструктора, который пытается инициализировать элемент данных (лямбда), используя всю py_catch<..> объект (вместо его func членом).

Простое, но, возможно, не оптимальное решение — предоставить еще один экземпляр-ctor для неконстантных значений py_catch(py_catch&) = default;, Но тогда, когда вы используете наследование или пользовательские преобразования, шаблон конструктора все равно будет предпочтительным.

Другое решение заключается в использовании некоторого SFINAE в шаблоне конструктора; например проверить для is_same, is_base_of или что-то подобное (не забудьте remove_reference возможная ссылка от F2). is_convertible может также работать, но я подозреваю, что он будет рекурсивно пытаться использовать шаблон конструктора для его проверки.

4

Другие решения

Хорошо, я нашел лучшее решение, которое намекает на реальную проблему.

Я подозревал, что проблема заключается в копировании и перемещении ctors по какой-то причине, выбирая шаблон, а не дефолтные ctors. Это означало, что py_catch<lambda_types?> передается в шаблон ctor и пересылается в func,

Поэтому я добавил тест в ctor с использованием SFINAE, и это устранило проблему, так как она будет отклонять что-либо, кроме соответствия типов функций.

Вот так:

template
<
typename F2,
typename =typename std::enable_if<std::is_same<F2, F>::value, F>::type
>
py_catch(F2&& f)
:   func ( std::forward<F2>(f) )
{   }

Гадкий да.

Мне нужно подождать несколько дней, прежде чем я смогу пометить это правильно, так что если кто-то может сказать мне, почему он не выбирает defaultРедактируйте шаблон над шаблоном, тогда я отмечу это правильно.

1

По вопросам рекламы [email protected]