Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

avoid pointer-to-member-function for non-class type

I am writing a kind of container class, for which I would like to offer an apply method which evaluates a function on the content of the container.

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

The last line yields error: creating pointer to member function of non-class type ‘const int’. Even though I only instantiated Foo and not used apply at all. So my question is: How can I effectively remove the second overload whenever T is a non-class type?

Note: I also tried having only one overload taking a std::function<U(const T&)>. This kinda works, because both function-pointers and member-function-pointers can be converted to std::function, but this approach effectively disables template deduction for U which makes user-code less readable.

like image 761
Simon Avatar asked Dec 13 '22 16:12

Simon


2 Answers

Using std::invoke instead helps, it is much easier to implement and read

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>;

However, this won't compile if the functions are overloaded

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

The way around that is to use functors instead

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

Which also makes it more consistent with member function calls

Foo<Bar>{}.apply([](auto&& bar) -> decltype(auto) { return decltype(bar)(bar).f(); });
like image 54
Passer By Avatar answered Dec 25 '22 22:12

Passer By


In order to remove the second overload you'd need to make it a template and let SFINAE work, e. g. like this:

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)());
    }
};

Alternatively, you could remove the second overload altogether, and use lambda or 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(); });
}
like image 40
joe_chip Avatar answered Dec 25 '22 23:12

joe_chip