【问题标题】:Constructor overloads to accept any function构造函数重载以接受任何函数
【发布时间】:2017-06-04 19:16:22
【问题描述】:

我正在尝试创建一个类模板,其构造函数可以将任何类型的函数作为参数,也就是说,它需要一个函数指针(可以是成员函数指针)和相应的函数参数。此外,应该有一个static_assert 来检查函数返回类型(取自函数指针)是否与类模板参数类型匹配。因此,代码应如下所示:

template <class ReturnType>
struct Bar
{
    template <class RetType, class ... ParamType>
    Bar<ReturnType>(RetType (* func)(ParamType ...), ParamType && ... args) :
        package_(std::bind(func, std::forward<ParamType>(args) ...)),
        function_([this] { package_(); }),
        future_(package_.get_future())
    {
        static_assert(std::is_same<ReturnType, RetType>::value,
            "Type mismatch between class parameter type and constructor parameter type");
    }

    template <class RetType, class ObjType, class ... ParamType>
    Bar<ReturnType>(RetType (ObjType::* func)(ParamType ...), ObjType * obj, ParamType && ... args) :
        package_(std::bind(func, obj, std::forward<ParamType>(args) ...)),
        function_([this] { package_(); }),
        future_(package_.get_future())
    {
        static_assert(std::is_same<ReturnType, RetType>::value,
            "Type mismatch between class parameter type and constructor parameter type");
    }

    std::packaged_task<ReturnType()> package_;
    std::function<void()> function_;
    std::future<ReturnType> future_;
};

这个想法是代码针对这些情况进行编译,并允许(通过函数调用运算符)调用Bar::function_ 而不会出错:

struct Foo
{
    int foo(int i) {
        return i;
    }

    int foo() {
        return 1;
    }
};

int foo(int i)
{
    return i;
}

int foo()
{
    return 1;
}

int main()
{
    Foo f = Foo();

    Bar<int> b1(&Foo::foo, &f, 1);
    Bar<int> b2(&Foo::foo, &f);
    Bar<int> b3(foo, 1);
    Bar<int> b4(foo);

    return 0;
}

不幸的是,我在模板元编程方面的经验几乎为零,尽管我在 SO 中遇到了几个问题,并尝试了几种解决问题的方法,例如对构造函数使用更通用的方法

template <class RetType, class ... ParamType>
Bar<ReturnType>(RetType func, ParamType && ... args)

并将其与type_traits 结合以确定返回类型),我还没有找到一种方法来完成这项工作。我可以对允许此功能的构造函数进行哪些更改?

编辑:

max66 的回答解决了我原来的问题,但是,出现了一个新问题,我在上一个问题中没有考虑过。我还希望能够将变量传递给构造函数,如下所示:

int main()
{
    Foo f = Foo();
    int i = 1;

    Bar<int> b1(&Foo::foo, &f, i); // Error
    Bar<int> b2(&Foo::foo, &f, 1); // Ok
    Bar<int> b3(&Foo::foo, &f); // Ok
    Bar<int> b4(foo, i); // Error
    Bar<int> b5(foo, 1); // Ok
    Bar<int> b6(foo); // Ok

    return 0;
}

然而,事实上,在标有Error 的情况下会出现编译器错误。我猜这是因为构造函数中的参数func 使用ParamType 来确定其类型(在b1b4 的情况下与实际的ParamTypes 不匹配),但我不知道怎么解决...

【问题讨论】:

  • 如果你使用 C++17,你可以使用std::invoke 让你的生活更轻松。如果你使用 C++14,一些库实现了 std::invoke 的等价物(例如 Boost Hana 有 hana::apply
  • 您的示例是重载函数,因此您“不能”在不选择特定重载的情况下仅获取它们的地址。尽管如此,你可以让你的构造函数接受第一个参数并使用一个 trait 来确保它是一个函数类型,然后可变参数转发引用来获取参数的其余部分,最后使用类似 std::invoke 的服务
  • 我确实在使用 C++17 特性,但我对 std::invoke 了解不多。我将如何实现我打算使用它的功能?

标签: c++ templates c++17 constructor-overloading


【解决方案1】:

您可能想使用std::invoke。它为您处理成员函数指针和常规函数。

作为你可以做的事情的概述:

#include <functional>
#include <type_traits>
#include <utility>

template<typename F>
class Bar
{
    F f_;

public:
    template<typename TF>
    Bar(TF && f)
        : f_{ std::forward<TF>(f) }
    {}

    template<typename... Args>
    decltype(auto) operator()(Args &&... args) {
        return std::invoke(f_, std::forward<Args>(args)...);
    }
};

template<typename F>
auto make_bar(F && f)
{
    return Bar<std::decay_t<F>>{ std::forward<F>(f) };
}

可以这样使用:

auto b1 = make_bar(&f);
auto result = b1(myArg1, myArg2); // etc

auto b2 = make_bar(&Foo::fn);
auto result = b1(foo, arg1);

至少,我建议让Bar 将函数对象类型作为模板参数,这样您就不必使用std::function,但如果您确实想使用您拥有的确切调用语法,也可以使用std::invokestd::invoke_result 来完成。

【讨论】:

  • max66 answer 解决了我的问题(结果证明这很简单),但这是很好的信息,我会研究它并可能使用它,谢谢。 +1
【解决方案2】:

对不起,但是...如果您希望函数的返回类型等于类的模板参数...为什么不简单地强加呢?

我的意思是……你可以用ReturnType代替RetType,如下

template <typename ReturnType>
struct Bar
{
    template <typename ... ParamType>
    Bar<ReturnType> (ReturnType (*func)(ParamType ...), ParamType && ... args)
      : package_(std::bind(func, std::forward<ParamType>(args) ...)),
        function_([this] { package_(); }),
        future_(package_.get_future())
     { }

    template <typename ObjType, typename ... ParamType>
    Bar<ReturnType> (ReturnType (ObjType::* func)(ParamType ...),
                     ObjType * obj, ParamType && ... args)
      : package_(std::bind(func, obj, std::forward<ParamType>(args) ...)),
        function_([this] { package_(); }),
        future_(package_.get_future())
     { }

-- 编辑--

解决第二个问题,如果你对移动参数不感兴趣,可以扔掉std::forward&amp;&amp;,直接写

template <typename ReturnType>
struct Bar
{
    template <typename ... ParamType>
    Bar<ReturnType> (ReturnType (*func)(ParamType ...), 
                     ParamType const & ... args)
      : package_(std::bind(func, args...)),
        function_([this] { package_(); }),
        future_(package_.get_future())
     { }

    template <typename ObjType, typename ... ParamType>
    Bar<ReturnType> (ReturnType (ObjType::* func)(ParamType ...),
                     ObjType * obj, ParamType const & ... args)
      : package_(std::bind(func, obj, args...)),
        function_([this] { package_(); }),
        future_(package_.get_future())
     { }

【讨论】:

  • Sigh,当然,这确实很好地解决了我的问题并允许我正在寻找的功能,我被我之前的实现所吸引(一个我在问题中提到的更通用的构造函数),我忘记了我不再需要 RetType 参数,+1,谢谢
  • 您能看看我对这个问题所做的修改吗?
  • @ShinuziKyura - (新)问题是调用b4(foo, i);,第二个参数类型推导出为int &amp;(引用int)其中foo()收到int(不参考);所以错误。 b1() 也有同样的问题。我不擅长参考,所以我不知道如何以一般方式解决这个问题,但是......你到底想获得什么?
  • 我正在努力让b1b4 也可以编译,就像我们可以通过给它一个变量来调用一个接受int 的函数一样int i;(就像你说的,这将是对 int 的引用)。同样,当函数接收到引用时,应该不可能 i.e. 给它一个 const 引用。这并不重要,你的回答已经解决了更大的问题,我可以忍受它,但如果有办法实现我刚才描述的,我很想知道。
  • @ShinuziKyura - 但我看到你使用&amp;&amp;std::forward;你有兴趣接收到的参数可以移动吗?或者您可以接受仅接收(通过示例)const 引用的函数?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-10
  • 2019-05-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多