Недостаток std :: mem_fn по сравнению с ручным функтором

Я наткнулся на случай, когда 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 таким образом, что этот вариант использования работает так же, как и с обернутой вручную оберткой?

1

Решение

Возможно, да, но это не так, как указано в стандарте 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, но это не влияет на ваш пример.)

5

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

Других решений пока нет …

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