模板回调的包装器?

时间:2017-07-09 17:30:01

标签: c++ templates lambda auto

立即回拨

考虑以下示例:

template <typename lambda> void call_me_back(const lambda & callback)
{
    // Very complicated calculation to find the meaning of everything

    callback(42);
}

int main()
{
    call_me_back([](const int & value)
    {
        std :: cout << value << std :: endl;
    });
}

在这里,我向call_me_back提供了一个接受int的lambda。经过一段长时间的计算后,call_me_back会调用callback,并callback将其打印出来。像蛋糕一样简单。

现在,根据执行情况,call_me_back需要使用callback或其他类型调用int。我们可以将前面的例子编辑为

template <typename lambda> void call_me_back(const lambda & callback)
{
    // Very complicated calculation to find the meaning of everything

    if(rand() % 2)
        callback(42);
    else
        callback("Kill all the humans.");
}

int main()
{
    call_me_back([](const auto & value)
    {
        std :: cout << value << std :: endl;
    });
}

到目前为止一切顺利。现在callback可以执行各种操作并根据其类型处理value

延迟回叫

现在,回到第一个例子。假设call_me_back尚未准备好立即使用callback拨打int。但它可以做的是存储某处的callback,然后再调用它。

例如:

std :: function <void(const int &)> the_callback;

template <typename lambda> void call_me_back(const lambda & callback)
{
    the_callback = callback;
}

void ready_to_answer()
{
    the_callback(42);
}

int main()
{
    call_me_back([](const auto & value)
    {
        std :: cout << value << std :: endl;
    });
}

现在,callback不是立即调用call_me_back,而是将callback存储在std :: function <void(const int &)>对象中(我知道,它在全球范围内,请耐心等待)。然后很多事情都会发生,在某些时候有人可以调用ready_to_answer,它会检索先前存储的回调并调用它。例如,ready_to_answer可以被另一个线程调用,但是有很多原因可以解释为什么需要这样的范例,其中回调可以存储并在以后调用。

问题

如果我想实现第二个示例,但延迟回调会怎样?。我似乎无法理解这一点。

我可以想象std :: function是通过接受某种特定类型的虚拟调用运算符实现的。然后std :: function将指针/引用包装到模板包装类中,该类存储实际的lambda并通过将其参数转发到它存储的lambda来实现调用操作符。好,易于。 但我不能拥有模板虚拟方法!

我尝试过各种各样的解决方案,但我找不到任何合理的解决方案。这真的不可能吗?是否不可能有一些外部提供的lambda接受存储在某处的const auto &参数,然后再调用它?

3 个答案:

答案 0 :(得分:7)

你是对的,对于一组无限的类型是不可能的,但如果事先知道所有类型,你就可以做到:

std :: function <void(const std :: variant<int, std :: string> &)> the_callback;

template <typename lambda> void call_me_back(const lambda & callback)
{
    the_callback = [callback](const auto & arg)
    {
        std :: visit(callback, arg);
    };
}

template <typename T> void ready_to_answer(const T & x)
{
    the_callback(x);
}

int main()
{
    call_me_back([](const auto & value)
    {
        std :: cout << value << std :: endl;
    });

    if (std :: rand() % 2)
    {
        ready_to_answer(42);
    }
    else
    {
        ready_to_answer("Hi!");
    }
}

答案 1 :(得分:1)

在第二个示例中,如果没有传递函数,则使用两个方法operator()(int)operator(char const (&)[..])传递对匿名lambda对象的引用。因此,要存储这种回调,您需要复制lambda对象或存储特定的回调方法,即使用多个::std::function和相应的签名。实际上,为这两种情况明确传递两个回调会更清楚。

std::function< void (int) > delalyed_int_cb;
std::function< void (const char *) > delalyed_str_cb;

template< typename callable_taking_int, typename callable_taking_string > void
call_me_back(callable_taking_int && int_cb, callable_taking_string && str_cb)
{
    delalyed_int_cb = std::forward< callable_taking_int >(int_cb);
    delalyed_str_cb = std::forward< callable_taking_str >(str_cb);
    ...
}

void
ready_to_answer()
{
    if(rand() % 2)
    {
        delalyed_int_cb(42);
    }
    else
    {
        delalyed_str_cb("Kill all the humans.");
    }
}

答案 2 :(得分:1)

您可以实现一个为您存储lambda的类模板。

#include<iostream>

template<typename L>
struct Telephone
{
    L l;

    Telephone(L l) : l{std::move(l)} {}

    template<typename... Args>
    decltype(auto) ready_to_answer(Args&&... args)
    {
        return l(std::forward<Args>(args)...);   
    }
};

template<typename L>
auto call_me_back(L&& l)
{
    return Telephone<std::decay_t<L>>(std::forward<L>(l));
}

int main()
{
    auto telephone = call_me_back([](auto x){ std::cout << x << std::endl; });
    telephone.ready_to_answer(42);
    telephone.ready_to_answer("Kill all the humans.");
}

这有一个缺点,Telephone在lambda上是模板化的,并且对于每个lambda都是不同的类型。

如果您事先知道函数签名的外观,那么您可以从公共基类继承Telephone并使用虚拟非模板方法。

Telephone的某些自动构建和管理进行了一些操作,您基本上实现了另一个std::function,但使用了自定义签名

struct Phone
{
    // all Telephone<L> inherits from Phone and have these methods

    virtual void ready_to_answer(int) = 0;
    virtual void ready_to_answer(const char*) = 0;
};

struct Spouse
{
    std::unique_ptr<Phone> phone;

    template<typename L>
    Spouse(L&& l) : phone{ new Telephone<std::decay_t<L>>{std::forward<L>(l)} } {}

    void ready_to_answer(int i) { phone->ready_to_answer(i); }
    void ready_to_answer(const char* str) { phone->ready_to_answer(str); }
};

编辑:我继续实施了std::function的增强版本,该版本接受任意数量的签名,因此您可以编写

function<void (int), void (std::string)> callback =
    [](auto x){std::cout << x << std::endl;};

callback(42);
callback("Kill all humans!");

Live

可以说实施并非易事,而且这里有太多的解释。