Идеальная пересылка лямбда-аргументов функции-члену, где функция-член является нетиповым параметром шаблона - PullRequest
0 голосов
/ 06 июня 2018

Контекст

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

#include <iostream>
#include <string>
#include <utility>

template <class ClassT, class... ArgsT>
auto getCallbackPtr(ClassT* obj, void(ClassT::* memfn)(ArgsT...))
{
    return [obj, memfn](ArgsT&&... args) {
        (obj->*memfn)(std::forward<ArgsT>(args)...);
    };
}
template <auto memFn, class ClassT>
auto getCallbackTemplate(ClassT* obj)
{
    return [obj](auto&&... args){
        return (obj->*memFn)(std::forward<decltype(args)>(args)...);
    };
}
template <auto memFn, class ClassT, class... ArgsT>
auto getCallbackRedundant(ClassT* obj)
{
    return [obj](ArgsT&&... args){
        return (obj->*memFn)(std::forward<ArgsT&&>(args)...);
    };
}

// Example of use
class Foo {
public:
    void bar(size_t& x, const std::string& s) { x=s.size(); }
};
int main() {
    Foo f; 
    auto c1 = getCallbackPtr(&f, &Foo::bar);
    size_t x1; c1(x1, "123"); std::cout << "c1:" << x1 << "\n";
    auto c2 = getCallbackTemplate<&Foo::bar>(&f);
    size_t x2; c2(x2, "123"); std::cout << "c2:" << x2 << "\n";
    auto c3 = getCallbackRedundant<&Foo::bar, Foo, size_t&, const std::string&>(&f);
    size_t x3; c3(x3, "123"); std::cout << "c3:" << x3 << "\n";
}

Вопрос (вкратце)

Я хотел бы функцию, которая объединяет различные аспекты трех вышеупомянутых функций:

  • В отличие от getCallbackPtr(). * Она должна принимать функцию-член в качестве параметра шаблона времени компиляции.
  • Ее operator() не должна быть шаблонной функцией, в отличие от getCallbackTemplate().
  • .параметры (кроме указателя на функцию-член) должны выводиться из использования функции, в отличие от getCallbackRedundant().

Некоторые подробности

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

  • Оптимизатор, вероятно, будет вызывать функцию-член напрямую, а не через указатель на функцию.Фактически, поскольку это единственное место, из которого вызывается функция-член, она может даже быть встроена в лямбду компилятором.
  • Полученный объект функции меньше (один указатель, а не один указатель плюс один членуказатель функции) и т. д., более вероятно, вписывается в std::function (оптимизация небольших объектов).

Вот проблемы с getCallbackTemplate(), который имеет шаблон operator():

  • Не работает с Visual Studio.Это шоу-стоппер для меня.(Ошибка error C3533: a parameter cannot have a type that contains 'auto', в отношении template <auto memFn, class ClassT>.)
  • Если переданы неправильные типы аргументов, я подозреваю, что это приведет к более сложной и запутанной ошибке компилятора, чем не шаблонная operator() (правда, это всего лишь догадка).
  • Шаблонный operator() не способен принимать списки инициализаторов для аргументов.Для меня это совсем не проблема, но я упомяну об этом для записи.

Я думаю, что причины, по которым нужно выводить параметры шаблона, достаточно ясны: getCallbackRedundant() отвлекает многословно и сложнее в использовании.

Можно ли это сделать?Как?

Ответы [ 3 ]

0 голосов
/ 06 июня 2018

Один простой способ вывести аргументы - использовать частичную специализацию шаблонов.

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

Частично специализируется на типе, а оттуда все остальное просто.

#include <iostream>
#include <string>

template <auto memFnPtr, class memFn>
struct getCallbackTemplate;

template <auto memFnPtr, class Ret, class ClassT, class... Args>
struct getCallbackTemplate<memFnPtr, Ret(ClassT::*)(Args...)>
{
    getCallbackTemplate (ClassT* obj) : m_obj(obj) {}

    Ret operator()(Args... args) {
        return (m_obj->*memFnPtr)(std::forward<Args>(args)...);
    }

    ClassT* m_obj;
};

template <auto memFn, class ClassT>
auto getCallback(ClassT* obj) {
    return getCallbackTemplate<memFn, decltype(memFn)>(obj);
}

class Foo {
public:
    void bar(std::size_t& x, const std::string& s) { x=s.size(); }
};

int main() {
    Foo f; 
    auto c1 = getCallback<&Foo::bar>(&f);
    size_t x1; c1(x1, "123"); std::cout << "c1:" << x1 << "\n";
}
0 голосов
/ 07 июня 2018

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

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

template <auto memFn, class ClassT, class RetT, class... ArgsT>
inline auto getCallbackInner(ClassT* obj, RetT(ClassT::*)(ArgsT...))
{
    return [obj](ArgsT... args)->RetT {
        return (obj->*memFn)(std::forward<ArgsT>(args)...);
    };
}
template <auto memFn, class ClassT>
auto getCallback(ClassT* obj)
{
    return getCallbackInner<memFn, ClassT>(obj, memFn);
}

Как и в двух других ответах, в этом все еще используется параметр автоматического шаблона из стандарта C ++ 17,так что это не работает в Visual Studio.Это жаль, но кажется, что это просто невозможно с помощью только C ++ 14.

Сноска:

Еще один ответ, который является более субъективным, но, вероятно, наиболее правильнымЯ просто не должен пытаться передать функцию-член в качестве параметра шаблона.Исходный getCallbackPtr(), который просто связывает указатель на функцию-член в лямбду, вероятно, будет понятен большему количеству людей (а также большим количеством компиляторов), чем любая другая возможность.Затраты на производительность косвенного обращения через указатель на функцию-член, вероятно, будут незначительными, в то время как затраты на обслуживание использования уловок шаблонов высоки, поэтому я думаю, что на самом деле я буду использовать эту версию на практике, если не будет очевидных затрат на производительность.

0 голосов
/ 06 июня 2018

Я хотел бы функцию, которая сочетает в себе различные аспекты вышеупомянутых трех функций [...]

Если я правильно понимаю, что вы хотите ... Мне кажется, чтовозможно, но я вижу только запутанное решение.

Надеясь, что кто-то другой может предложить более простой способ, я использовал несколько помощников: объявленная только шаблонная функция gth1() для обнаружения Args... изуказатель метода

template <typename ClassT, typename ... ArgsT>
constexpr auto gth1 (void(ClassT::*)(ArgsT...)) -> std::tuple<ArgsT...>;

и специализация шаблона gth2 struct со статическим методом, который создает и возвращает лямбду (с поправкой Холта: спасибо!)

template <typename, typename, auto>
struct gth2;

template <typename ClassT, typename ... ArgsT, auto memFn>
struct gth2<ClassT, std::tuple<ArgsT...>, memFn>
 { 
   static auto getLambda (ClassT * obj)
    { return [obj](ArgsT ... args)
       { return (obj->*memFn)(std::forward<ArgsT>(args)...); }; }
 };

Теперь выможно написать getCallback() функцию следующим образом

template <auto memFn, typename ClassT>
auto getCallback (ClassT * obj)
 { return gth2<ClassT, decltype(gth1(memFn)), memFn>::getLambda(obj); } 

Ниже приведен полный рабочий пример

#include <iostream>

template <typename, typename, auto>
struct gth2;

template <typename ClassT, typename ... ArgsT, auto memFn>
struct gth2<ClassT, std::tuple<ArgsT...>, memFn>
 { 
   static auto getLambda (ClassT * obj)
    { return [obj](ArgsT ... args)
       { return (obj->*memFn)(std::forward<ArgsT>(args)...); }; }
 };

template <typename ClassT, typename ... ArgsT>
constexpr auto gth1 (void(ClassT::*)(ArgsT...)) -> std::tuple<ArgsT...>;

template <auto memFn, typename ClassT>
auto getCallback (ClassT * obj)
 { return gth2<ClassT, decltype(gth1(memFn)), memFn>::getLambda(obj); } 

// Example of use
struct Foo
 { void bar(size_t& x, const std::string& s) { x=s.size(); } };

int main ()
 {
   Foo f;

   auto l { getCallback<&Foo::bar>(&f) };

   size_t x;

   l(x, "1234567");

   std::cout << x << "\n";
 }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...