【问题标题】:avoid pointer-to-member-function for non-class type避免非类类型的指向成员函数的指针
【发布时间】:2018-06-22 15:58:24
【问题描述】:

我正在编写一种容器类,我想为此提供一个apply 方法,该方法评估容器内容的函数。

template<typename T>
struct Foo
{
    T val;

    /** apply a free function */
    template<typename U> Foo<U> apply(U(*fun)(const T&))
    {
        return Foo<U>(fun(val));
    }

    /** apply a member function */
    template<typename U> Foo<U> apply(U (T::*fun)() const)
    {
        return Foo<U>((val.*fun)());
    }
};

struct Bar{};
template class Foo<Bar>; // this compiles
//template class Foo<int>; // this produces an error

最后一行产生error: creating pointer to member function of non-class type ‘const int’尽管我只实例化了 Foo 而根本没有使用 apply。所以我的问题是:当T 是非类类型时,如何有效地移除第二个重载?

注意:我还尝试让一个重载采用std::function&lt;U(const T&amp;)&gt;。这有点工作,因为函数指针和成员函数指针都可以转换为std::function,但是这种方法有效地禁用了U 的模板推导,这使得用户代码的可读性降低。

【问题讨论】:

  • std::apply 在这里可能有用。
  • 如果有帮助的话,我建议不要使用成员函数指针,它们的混乱超出了想象。

标签: c++ templates function-pointers member-function-pointers


【解决方案1】:

使用std::invoke 会有所帮助,它更容易实现和阅读

template<typename T>
struct Foo
{
    T val;

    template<typename U> auto apply(U&& fun)
    {
        return Foo<std::invoke_result_t<U, T>>{std::invoke(std::forward<U>(fun), val)};
    }
};

struct Bar{};
template class Foo<Bar>;
template class Foo<int>;

但是,如果函数重载,这将无法编译

int f();
double f(const Bar&);
Foo<Bar>{}.apply(f);  // Doesn't compile

解决方法是使用仿函数

Foo<Bar>{}.apply([](auto&& bar) -> decltype(auto) { return f(decltype(bar)(bar)); });

这也使它与成员函数调用更加一致

Foo<Bar>{}.apply([](auto&& bar) -> decltype(auto) { return decltype(bar)(bar).f(); });

【讨论】:

  • return f(bar); ---> return f(std::forward&lt;decltype(bar)&gt;(bar))
  • @liliscent 我不想让一千万条通用编程样板代码让整个事情陷入困境。其实一开始我也不想写decltype(auto)。但以完整性的名义,给你。
  • 作为一个答案,我同意。但是,如果您开始编写decltype(auto)auto&amp;&amp;,我认为需要完整性。 +1
  • 这确实比我添加多个重载的方法更易于阅读并且可能更灵活。所以我将使用(并因此接受)这个答案。但也非常感谢其他回答者,这很有教育意义。
  • c++17 甚至允许return Foo{std::invoke(std::forward&lt;U&gt;(fun), val)}
【解决方案2】:

为了消除第二个重载,您需要将其设为模板并让 SFINAE 工作,例如。 G。像这样:

template<typename T>
struct Foo
{
    T val;

    //...

    /** apply a member function */
    template<typename U, typename ObjT>
    Foo<U> apply(U (ObjT::*fun)() const)
    {
        return Foo<U>((val.*fun)());
    }
};

或者,您可以完全删除第二个重载,并使用 lambda 或 std::bind:

#include <functional> // for std::bind

template<typename T>
struct Foo
{
    T val;

    /** apply a member function */
    template<typename U, typename FuncT>
    Foo<U> apply(FuncT&& f)
    {
        return {f(val)};
    }
};

struct SomeType
{
    int getFive() { return 5; }
};

int main()
{
    Foo<SomeType> obj;

    obj.apply<int>(std::bind(&SomeType::getFive, std::placeholders::_1));
    obj.apply<int>([](SomeType& obj) { return obj.getFive(); });
}

【讨论】:

    【解决方案3】:

    当 T 是非类类型时,如何有效地移除第二个重载?

    如果您至少可以使用 C++11(并且如果您尝试过 std::function 我想您可以使用它),您可以将 SFINAE 与 std::enable_if 一起使用

       template <typename U, typename V>
       typename std::enable_if<std::is_class<V>{}
                            && std::is_same<V, T>{}, Foo<U>>::type
          apply(U (V::*fun)() const)
        { return Foo<U>((val.*fun)()); }
    

    强加T 是一个类。

    注意不能直接查T,也就是类的模板参数,但要传入一个V类型,具体方法的模板类型。

    但您也可以强制要求 TV 是同一类型 (&amp;&amp; std::is_same&lt;V, T&gt;{})。

    【讨论】:

    • 这里真的不需要enable_if。如果VT 不同,没有它,编译器将在(val.*fun)() 处抛出错误。您也不需要std::is_class,具有U (V::*)() 类型的参数足以导致非类类型的替换失败。
    • @joe_chip 对此投反对票是没有意义的。 enable_if 可以作为自我记录。此外,这两个具有不同的语义,您可能希望对失败的类型进行另一个重载,而不是硬错误。虽然我同意你在std::is_class 部分。
    • 恕我直言,评论(或将V 更改为例如SameAsT)对于文档来说会更好。在这里,使用enable_if 只会导致编译器产生更多令人困惑的错误。但是,是的,如果您想再重载一次,那么enable_if 将是不错的选择。
    • @joe_chip - 你的观点是正确的,但我已经回答了 OP 的一个精确问题:“当 T 是非类类型时,如何有效地消除第二个重载?”
    • @max66 好吧,不赞成,虽然我真的认为 enable_if 在这里是一种滥用。
    猜你喜欢
    • 1970-01-01
    • 2010-11-02
    • 1970-01-01
    • 2017-07-27
    • 2013-06-06
    • 1970-01-01
    • 1970-01-01
    • 2015-11-18
    相关资源
    最近更新 更多