Функция, принимающая указатель на метод независимо от константы

Aug 19 2020

Я хочу реализовать универсальную функцию, которая будет использовать ссылку на объект и указатель на свою функцию-член и вызывать ее. Однако я не могу этого сделать, если в моем классе есть как константные, так и неконстантные методы, так как мне нужно предоставить две перегрузки:

template<typename Ret, typename Class, typename ...Us>
Ret callMethod(Class &object, Ret (Class::*method)(Us...))
{
    return (object.*method)(Us{}...);
}

template<typename Ret, typename Class, typename ...Us>
Ret callMethod(Class &object, Ret (Class::*method)(Us...) const)
{
    return (object.*method)(Us{}...);
}

Есть ли способ написать только одну функцию-шаблон, которая будет принимать как константные, так и неконстантные указатели на методы, чтобы мне не приходилось писать код дважды? Я использую C ++ 14.

Для более широкой картины, в конечном итоге я хочу передать третий параметр, буфер данных, из которого будут извлечены аргументы метода - следовательно, функция шаблона будет обрабатывать его как можно более обобщенно.

Ответы

2 TedLyngmo Aug 19 2020 at 19:58

Вот альтернатива C ++ 14 без использования std::function.

то, что я хочу в конечном итоге достичь, - это передать третий параметр, буфер данных, из которого будут извлечены аргументы метода - следовательно, функция шаблона для обработки его как можно более обобщенно

То, что вы используете на сайте вызова, будет идеально перенаправлено сюда:

template<typename Class, typename Func, typename... Args>
decltype(auto) callMethod_impl(Class& object, Func method, Args&&... args) {
    return (object.*method)(std::forward<Args>(args)...);
}


template<typename Ret, typename Class, typename... Us, typename... Args>
Ret callMethod(Class& object, Ret(Class::*method)(Us...), Args&&... args) {
    return callMethod_impl(object, method, std::forward<Args>(args)...);
}

template<typename Ret, typename Class, typename... Us, typename... Args>
Ret callMethod(const Class& object, Ret(Class::*method)(Us...) const, Args&&... args) {
    return callMethod_impl(object, method, std::forward<Args>(args)...);
}

Демо

Если вам нужно Retв callMethod_impl, просто добавьте его в качестве параметра шаблона и назовите его как callMethod_impl<Ret>(...)от callMethodперегрузок ( Demo ).

3 JanSchultke Aug 19 2020 at 17:28

Короткий ответ: не реализуйте это самостоятельно, это уже было сделано за вас в виде std::invoke:

#include <functional>

struct A {
    void foo(int x);
    void bar(int x) const;
};

void example() {
    A a;
    std::invoke(&A::foo, a, 3); 
    std::invoke(&A::bar, a, 3); 
}

Видя, что вы ретроспективно добавили тег C ++ 14, в документации std::invokeесть образец реализации, который вы можете использовать в своем проекте.

1 Jodocus Aug 19 2020 at 17:59

Без него std::invokeвы можете найти только некоторые более или менее общие обходные пути, основанные на стирании типа. Использование std::functionпозволяет вам создать прокси-функцию, которая будет относиться к вашим функциям на равных условиях:

#include <iostream>
#include <functional>

template<typename Ret, typename ...Us>
Ret callMethod_impl(std::function<Ret(Us...)> f) {
    // common implementation here
    return f(Us{}...);
}

template<typename Ret, typename Class, typename ...Us>
Ret callMethod(Class &object, Ret (Class::*method)(Us...)) {
    return callMethod_impl(std::function<Ret(Us...)>(std::bind(method, object)));
}

template<typename Ret, typename Class, typename ...Us>
Ret callMethod(const Class &object, Ret (Class::*method)(Us...) const) {
    return callMethod_impl(std::function<Ret(Us...)>(std::bind(method, object)));
}

struct Foo {
    int bar() const { return 1; }
    float bar() { return 2.1f; };
};

int main() {
    Foo f;
    std::cout << callMethod(f, &Foo::bar) << std::endl;
}

Обратите внимание, что volatileфункции (+ комбинации) здесь не рассматриваются, но, возможно, вам не нужна полная общность, а скорее прагматическое решение.