Преобразование между std::function с разными сигнатурами (T* arg в void* arg)

Есть ли способ преобразовать std::function с аргументом T* в аналогичный с аргументом void*? Это кажется возможным, поскольку вызовы должны быть совместимы на двоичном уровне.

Например, как я могу заставить это работать, не превращая Producer в шаблон и не теряя безопасность типов?

#include <functional>

struct Producer {
    // produces an int from a callable and an address
    template<class Src>
    Producer(Src& src, std::function<int (Src*)> f)
      : arg_(&src),
        f_(f)
    {}

    int operator()() {
        return f_(arg_);
    }

    // type erasure through void* but still type safe since ctor
    // checks that *arg_ and f are consistent
    void* arg_;
    std::function<int (void*)> f_;
};

int func1(char* c) {
    return *c;
}

int func2(int* i) {
    return *i;
}


int try_it() {
    char c = 'a';
    char i = 5;
    // we want to make these work
    Producer p1(c, func1);
    Producer p2(i, func2);
    // but we still want this to fail to compile
    // Producer p3(c, func2);
    return p1() + p2();
}

Изменить: Решение с определенным UDB, но правильным поведением. :-/


person Praxeolitic    schedule 13.11.2015    source источник
comment
It seems possible since the calls should be compatible at the binary level. Хотя оба указателя (вероятно) не различаются в памяти, в некотором роде это определенно UB. Что должна делать вызываемая функция с переданным not-T там, где ожидается T? ... Я не понимаю, почему вы беспокоитесь о потере безопасности типов, когда вы собираетесь ее потерять.   -  person deviantfan    schedule 13.11.2015
comment
@deviantfan Ты уверен в UDB? Пока приведения к void* и обратно имеют правильный тип, я думаю, что это законно. ctor предотвращает получение не-T, но если бы это было не так, тогда это был бы UDB. Я не понимаю, почему вы говорите, что это не безопасно для типов.   -  person Praxeolitic    schedule 13.11.2015
comment
Я бы использовал bind, чтобы вызовы функций имели одинаковую подпись. Таким образом, Producer просто вызывает функциональные объекты без дополнительных аргументов.   -  person Anon Mail    schedule 13.11.2015
comment
@AnonMail Есть ли способ сделать это без пользователя Producer? О, и в реальной ситуации, когда это произошло, у меня было две функции, которые использовали void*.   -  person Praxeolitic    schedule 13.11.2015
comment
@Praxeolitic Хотя обработка в функции может быть в порядке, как вы собираетесь преобразовывать объекты функции в первую очередь? Приведение адресов к другому типу здесь определенно является UB, и просто передача одной вещи в качестве параметра конструктора другой здесь не будет компилироваться (или это для вас?)   -  person deviantfan    schedule 13.11.2015
comment
@Praxeolitic Возможно, с функцией шаблона члена. Но я не настолько продвинут. Что бы я сделал, так это сначала начать с перегруженных конструкторов для различных типов, которые вы хотите использовать в качестве входных данных, а затем, возможно, сократить код с помощью функции-члена шаблона.   -  person Anon Mail    schedule 13.11.2015
comment
@deviantfan Ха, нет, определенно не компилируется. Я не совсем понимаю ваш комментарий, но я думаю, что std::function сам обрабатывает стирание фактического типа объекта функции. Здесь нам просто нужно одно преобразование адреса объекта, которое не меняет того, как std::function вызывает вызываемый объект (как бы это ни было).   -  person Praxeolitic    schedule 13.11.2015
comment
Я предполагаю, что вы имеете в виду int i вместо char i, как вы написали в своем связанном решении. Я пытался внести правки, но они были отклонены, потому что, очевидно, вместо этого я должен был прокомментировать.   -  person Ben Russell    schedule 17.11.2015


Ответы (1)


Вы можете просто не воспринимать аргумент как std::function, и тогда вам не придется об этом беспокоиться. Возьмите любой тип F, если вы можете вызвать его с помощью Src* и он возвращает что-то, что можно преобразовать в int. Начните с дружественного к SFINAE std::result_of_t (беззастенчиво позаимствованного у Yakk):

template<class F, class...Args>
using invoke_result = decltype( std::declval<F>()(std::declval<Args>()...));

И используйте это для SFINAE вашего конструктора:

template<class Src,
         class F,
         class = std::enable_if_t<
            std::is_convertible<invoke_result<F, Src*>, int>::value
         >>
Producer(Src& src, F f)
    : arg_(&src)
    , f_([f = std::move(f)](void* arg){
        return f(static_cast<Src*>(arg));  
    })
{ }

УБ там нет. Это также правильно отклоняет ваше дело p3. Кроме того, вам даже не нужен arg_, если только вы не используете его по какой-то другой причине. Сохранить f_ как:

std::function<int ()> f_;

и вставьте в него Src:

template<class Src,
         class F,
         class = std::enable_if_t<
            std::is_convertible<invoke_result<F, Src*>, int>::value
         >>
Producer(Src& src, F f)
    : f_([&src, f = std::move(f)](){
        return f(&src);  
    })
{ }
person Barry    schedule 13.11.2015