Я наткнулся на случай, когда std::mem_fn
не может сделать то, что может сделать функция-оболочка, созданная вручную. Это происходит, когда функция-обертка используется для чего-то, что не относится к классу метода, а является типом, неявно преобразуемым в него:
#include <functional>
struct A
{
};
struct B
{
B(A); // implicit conversion from A to B
void foo() const;
};auto foo1 = std::mem_fn(&B::foo); // std::mem_fn
void foo2(const B& b) { b.foo(); } // hand-rolled wrapper
int main()
{
A a;
foo1(a); // doesn't work
foo2(a); // works fine
}
Ошибка компилятора для вызова foo1 следующая (с GCC 4.8):
In file included from test.cpp:1:0:
functional: In instantiation of '_Res std::_Mem_fn<_Res (_Class::*)(_ArgTypes ...)const>::_M_call(_Tp&, const volatile void*, _ArgTypes ...) const [with _Tp = A; _Res = void; _Class = B; _ArgTypes = {}]':
functional:608:42: required from '_Res std::_Mem_fn<_Res (_Class::*)(_ArgTypes ...)const>::operator()(_Tp&, _ArgTypes ...) const [with _Tp = A; _Res = void; _Class = B; _ArgTypes = {}]'
test.cpp:21:11: required from here
functional:586:13: error: no match for 'operator*' (operand type is 'A')
{ return ((*__ptr).*__pmf)(std::forward<_ArgTypes>(__args)...); }
^
Было бы возможно реализовать std::mem_fn
таким образом, что этот вариант использования работает так же, как и с обернутой вручную оберткой?
Возможно, да, но это не так, как указано в стандарте C ++ mem_fn
,
Стандарт говорит, что foo1(a)
звонки INVOKE(&B::foo, a)
где это определяется в [func.require] как:
определять
INVOKE (f, t1, t2, ..., tN)
следующее:
—(t1.*f)(t2, ..., tN)
когдаf
указатель на функцию-член классаT
а такжеt1
является объектом типаT
или ссылка на объект типаT
или ссылка на объект типа, производного отT
;
—((*t1).*f)(t2, ..., tN)
когда f является указателем на функцию-член классаT
а такжеt1
не является одним из типов, описанных в предыдущем пункте;
— …
Ваш случай не соответствует условиям первой пули, потому что a
не является объектом класса B
ни ссылка на B
или класс, полученный из B
, так что применяется вторая пуля, и поэтому она эквивалентна ((*a).*f)()
который не действителен.
Он определен таким образом, чтобы можно было использовать умные указатели, например,
auto foo1 = std::mem_fn(B::foo);
auto p = std::make_shared<B>();
foo1(p);
Определение INVOKE
(который также используется bind
, function
, async
и другие части библиотеки, которые создают обертки вызовов) означает, что при вызове обернутого указателя на член, если первый аргумент t1
не является T
тогда предполагается, что это какой-то указатель и разыменовывается. Это означает, что это работает с std::shared_ptr
а также std::unique_ptr
но и с типами, которые std::mem_fn
ничего не знает о, таких как boost::shared_ptr
а также MyVeryOwnSmartPtr
,
Чтобы ваш код работал, можно было бы добавить дополнительные случаи для обработки, когда t1
это не T
или тип, полученный из T
, но is_convertible<T>::value
верно, и для вызова T(t1).*f)()
, но это усложнит спецификацию и может иметь нежелательные последствия в некоторых случаях.
Ваша «обертка» будет принудительно вызывать неявное преобразование своего аргумента, но она не может обрабатывать умные указатели или значения типа B
, которые оба поддерживаются mem_fn
, Если у вас есть конкретный случай, когда вы хотите конвертировать A
возражает против B
чтобы вызвать функцию, а затем просто сделать это, общий mem_fn
шаблон не подходит, но он более гибкий и универсальный и работает во многих других ситуациях.
(Н.б. определение INVOKE
на самом деле неисправен, потому что разыменовывает std::reference_wrapper
объекты так же, как разыменовывает ваш a
аргумент. Я предложил исправить в http://cplusplus.github.com/LWG/lwg-active.html#2219, но это не влияет на ваш пример.)
Других решений пока нет …