Почему это происходит
Ответ max66 в основном объясняет, что происходит.Но может быть немного удивительно, что:
Вы можете неявно конвертировать из std::shared_ptr<int>
в std::shared_ptr<void>
, а не наоборот.
Вы можете неявно конвертировать из std::function<void(std::shared_ptr<void>)>
в std::function<void(std::shared_ptr<int>)>
, а не наоборот.
Вы можете неявно конвертировать из лямбды с типом аргумента std::shared_ptr<void>
в std::function<void(std::shared_ptr<int>)>
.
Нельзя неявно преобразовать лямбду с типом аргумента std::shared_ptr<int>
в std::function<void(std::shared_ptr<void>)>
.
Причина заключается в том, что при сравнении того, являются ли интерфейсы функций болееобщее или более конкретное правило состоит в том, что возвращаемые типы должны быть «ковариантными», а типы аргументов должны быть «контравариантными» ( Wikipedia ; см. также это SO Q & A ).То есть
Учитывая (псевдокод) типы интерфейсов функций
C func1(A1, A2, ..., An)
D func2(B1, B2, ..., Bn)
, тогда любая функция, которая является экземпляром типа func2
, также является экземпляромfunc1
введите, если D
можно преобразовать в C
, и каждый Ai
может преобразовать в соответствующий Bi
.
Чтобы понять, почему это так, рассмотрим, что произойдет, если мыразрешить преобразования function
в function
для типов std::function<std::shared_ptr<T>>
, а затем попытаться вызвать их.
Если преобразовать std::function<void(std::shared_ptr<void>)> a;
в std::function<void(std::shared_ptr<int>)> b;
, то b
действует как оболочкасодержащий копию a
и переадресацию на нее звонков.Тогда b
может быть вызван с любым std::shared_ptr<int> pi;
.Может ли он передать его в копию a
?Конечно, потому что он может конвертировать std::shared_ptr<int>
в std::shared_ptr<void>
.
Если мы конвертируем std::function<void(std::shared_ptr<int>)> c;
в std::function<void(std::shared_ptr<void>)> d;
, то d
действует как оболочка, содержащая копию c
и переадресация вызовов.к этому.Тогда d
может быть вызван с любым std::shared_ptr<void> pv;
.Может ли он передать его в копию c
?Не безопасно!Нет преобразования из std::shared_ptr<void>
в std::shared_ptr<int>
, и даже если мы представим, что d
каким-то образом пытается использовать std::static_pointer_cast
или подобное, pv
может вообще не указывать на int
.
Действующее стандартное правило, поскольку C ++ 17 ( [func.wrap.func.con] / 7 ) - это правило для std::function<R(ArgTypes...)>
шаблона конструктора
template<class F> function(F f);
Примечания: Этот шаблон конструктора не должен участвовать в разрешении перегрузки, если только f
не является Lvalue-callable для типов аргументов ArgTypes...
и типа возврата R
.
где "«Lvalue-callable» по существу означает, что выражение вызова функции с идеально перенаправленными аргументами заданных типов является допустимым, и если R
не cv void
, выражение может неявно преобразовываться в R
, плюс соображения для случаев, когда f
является указателем на член и / или некоторые типы аргументов являются std::reference_wrapper<X>
.
Это определение по существу автоматически проверяет контравариантные типы аргументов при попытке преобразования из любого вызываемого типа вstd::function
, поскольку он проверяет, являются ли типы аргументов целевого типа function
действительными аргументами для вызываемого типа источника (с учетом разрешенных неявных преобразований).
(до C ++ 17 std::function::function(F)
конструктор шаблона вообще не имел никаких ограничений в стиле SFINAE.Это было плохой новостью для таких ситуаций с перегрузкой и для шаблонов, которые пытались проверить, было ли преобразование допустимым.)
Обратите внимание, что на самом деле противоречивость типов аргументов проявляется по крайней мере в одной другой ситуации на языке C ++ (дажехотя это не разрешенное переопределение виртуальной функции).Указатель на значение члена можно рассматривать как функцию, которая принимает объект класса в качестве входных данных и возвращает элемент lvalue в качестве выходных данных.(Инициализация или присвоение std::function
от указателя на член будет интерпретировать значение именно таким образом.) И учитывая, что класс B
является общедоступной однозначной базой класса D
, мы имеем, что D*
можетнеявное преобразование в B*
, но не наоборот, а MemberType B::*
может преобразование в MemberType D::*
, но не наоборот.
Что делать
Тег, отправляющий max66предлагает одно решение.
Или для пути SFINAE,
void F(std::function<void(std::shared_ptr<void>)>);
void F(std::function<void(std::shared_ptr<int>)>);
// For a type that converts to function<void(shared_ptr<void>)>,
// call that overload, even though it likely also converts to
// function<void(shared_ptr<int>)>:
template <typename T>
std::enable_if_t<
std::is_convertible_v<T&&, std::function<void(std::shared_ptr<void>)>> &&
!std::is_same_v<std::decay_t<T>, std::function<void(std::shared_ptr<void>)>>>
F(T&& func)
{
F(std::function<void(std::shared_ptr<void>)>(std::forward<T>(func)));
}