4

There are a few questions on SO that address passing function pointers as parameters/arguments (here, here, here, etc.). In fact, I asked a related question the other day. However, this question is a little different.

My problem is that I am writing a class that I want to be extremely flexible.

What I have now works for non-member functions. It is posted below

template <typename T>
class MyClass
{
    private:
        typedef double (*firstFunctionPtr) (const T &var);

        typedef bool (*secondFunctionPtr)(const T &var);

        // Function pointers as member variables
        firstFunctionPtr _firstFunc;
        secondFunctionPtr _secondFunc;

    public:
        inline MyClass(firstFunctionPtr firstFunc, 
            secondFunctionPtr secondFunc);
};

template<typename T>
MyClass<T>::MyClass(firstFunctionPtr firstFunc, secondFunctionPtr secondFunc) :
    _firstFunc(firstFunc),
    _secondFunc(secondFunc),
    {}

However, this falls apart when I need to initialize with a pointer to a member function of some other, arbitrary, class, which, unfortunately for me, happens to be a common use case for my purposes.

This answer suggests that

In a proper C++ interface you might want to have a look at having your function take templated argument for function objects to use arbitrary class types.

However, I have not been able to make this compile. I've tried templating my typedefs (using the C++11 aliasing approach), and I've tried adding a second template parameter to the class to handle the calling class of those member functions, but neither approach has worked.

This Q/A seems to be getting towards what I'm trying to do, but I can't make heads or tails of it.

  1. Can someone please explain how I might modify my class to handle arbitrary member functions pointers being passed in?
  2. Furthermore, is it possible to make it so that it can handle either arbitrary member functions or non-member functions?
  3. Lastly, is it possible to do this with templates?

For the record, I'm trying to avoid using the functional header, but it may be a fool's errand not to use it.

Community
  • 1
  • 1
marcman
  • 3,233
  • 4
  • 36
  • 71
  • The problem with "function pointer to arbitrary class" is that you need an "object of arbitrary class" to use it. And no, `void*` won't work. You'll likely end up with `std::bind` and `std::function`, because that no longer has a dependency on an infinite set of arbitrary classes. – MSalters Feb 13 '17 at 11:39

3 Answers3

3

If you want MyClass to be a template that can hold both free function pointers of types:

double (*)(const T &var);
bool (*)(const T &var);

for some parameter type T, or alternatively member-function pointers of types:

double (C::*)(const T &var);
bool (C::*)(const T &var);

for some parameter types C and T then, MyClass must be parameterized by both T and C and you require two specializations:

  1. Where C is some non-class type
  2. Where C is any class type

In case (1), the non-class type C cannot possibly have member functions, so that one will implement the free-function pointer specialization.

In case (2), the class C could be one that has member functions, so that one will implement the member-function pointer specialization.

The obvious choice for a non-class type C is void. So we can make C default to void:

Primary template

template<typename T, typename C = void>
struct MyClass;

So that:

MyClass<T>

will be the free function pointer specialization for T, and:

MyClass<T,C>

for any C other than void, will be the member-function pointer specialization.

As you may know you can use std::enable_if and SFINAE to make the compiler chose one specialization of a class template or another, depending on whether one of its template parameters U satisfies some compiletime test. You could take that approach here, but another one is available that does not require that apparatus:

Starting with the primary template, we would just like to have:

Free function specialization

template<typename T>
struct MyClass<T>
{
    ... for free function pointers ...
};

and:

Member function specialization

template<typename T, typename C>
struct MyClass<T,C>
{
    ... for member function pointers ...
};

But we can't have just that, because the member function "specialization" has exactly the same template parameters as the primary template. Which means it isn't a specialization, and the compiler won't allow it.

You can easily remove that problem, however, simply by giving the primary template one more defaulting template parameter that it doesn't need, but whose presence allows both those specializations to stand.

New primary template

template <typename T, typename C = void, typename Default = void> 
struct MyClass;

So here is an illustrative solution:

// Primary template
template <typename T, typename C = void, typename Default = void> 
struct MyClass;

// Free function specialization
template <typename T>
struct MyClass<T>
{
    using firstFunctor_t = double(*)(T const &);
    using secondFunctor_t = bool(*)(T const &);

    MyClass(firstFunctor_t firstFunc, secondFunctor_t secondFunc)
    :   _firstFunc(firstFunc),
        _secondFunc(secondFunc)
    {}

    double callFirst(T const & var) {
        return _firstFunc(var);
    }

    bool callSecond(T const & var) {
        return _secondFunc(var);
    }

private:

    firstFunctor_t _firstFunc;
    secondFunctor_t _secondFunc;
};

// Member function specialization
template <typename T, typename C>
struct MyClass<T,C>
{
    using firstFunctor_t = double(C::*)(T const &);
    using secondFunctor_t = bool(C::*)(T const &) const;

    MyClass(firstFunctor_t firstFunc, secondFunctor_t secondFunc)
    :   _firstFunc(firstFunc),
        _secondFunc(secondFunc)
    {}

    double callFirst(C & obj, T const & var) {
        return (obj.*_firstFunc)(var);
    }

    double callFirst(C const & obj, T const & var) {
        auto & o = const_cast<C&>(obj);
        return (o.*_firstFunc)(var);
    }

    bool callSecond(C & obj, T const & var) {
        return (obj.*_secondFunc)(var);
    }

    bool callSecond(C const & obj, T const & var) {
        auto & o = const_cast<C&>(obj);
        return (o.*_secondFunc)(var);
    }

private:

    firstFunctor_t _firstFunc;
    secondFunctor_t _secondFunc;
};

In the member function specialization, notice a couple of points that you might not have considered:-

I decided that the second member function I want to store shall be a const member function. It's more than likely that a member function of C that take a T const & argument and returns bool will be a const member function, isn't it? And if so, then that const-ness has to be part of the member-function type definition that I use in the specialization:

using secondFunctor_t = bool(C::*)(T const &) const;

or attempts to instantiate the specialization with any bool (C::*)(T const &) const will fail to compile.

Also, I have provided two overloads for each of MyClass<T,C>::callFirst and MyClass<T,C>::callSecond, one with arguments:

C & obj, T const & var

and another with arguments:

C const & obj, T const & var

Without the second, attempts to call either MyClass<T,C>::callFirst or MyClass<T,C>::callSecond with an obj that is const will fail to compile.

For program to demo this solution you can append:

#include <iostream>
#include <string>

double foo(std::string const & s)
{
    return std::stod(s);
}

bool bar(std::string const & s)
{
    return s.size() > 0;
}

struct SomeClass
{
    SomeClass(){};
    double foo(std::string const & s) {
        return ::foo(s);
    }

    bool bar(std::string const & s) const {
        return ::bar(s);
    }
};

int main()
{
    MyClass<std::string> my0{foo,bar};
    std::cout << std::boolalpha;
    std::cout << my0.callFirst("1.11") << std::endl;
    std::cout << my0.callSecond("Hello World") << std::endl;

    MyClass<std::string,SomeClass> my1{&SomeClass::foo,&SomeClass::bar};
    SomeClass thing;
    std::cout << my1.callFirst(thing,"2.22") << std::endl;
    std::cout << my1.callSecond(thing,"Hello World") << std::endl;

    SomeClass const constThing;
    std::cout << my1.callFirst(constThing,"3.33") << std::endl;
    std::cout << my1.callSecond(constThing,"Hello World") << std::endl;
    return 0;
}

See it live

You said that you want this template to be "extremely flexible". The illustrated solution is fitted to your example, but you might be interested in know that it isn't nearly as flexible as you could get. For both free functions and member functions, with additional variadic template parameters, your template could store and call [member] functions with arbitary return types and arbitary numbers of arguments of arbitrary types. See this question and answer.

Community
  • 1
  • 1
Mike Kinghan
  • 55,740
  • 12
  • 153
  • 182
  • This is the sort of answer I was hoping for. Thanks! One (subjective) question though. Is it bad practice to have extraneous template params? It seems like it's asking for problems to have purposefully unused information – marcman Feb 13 '17 at 16:05
  • @marcman Hi. No, it's not a bad practice; it's stock-in-trade for TMP. Look (end of the examples) how [std::enable_if](http://en.cppreference.com/w/cpp/types/enable_if) is used to enable SFINAE-selection between class template specializations. *What it does* is append a defaulting template parameter to the primary template that has no use in any specialization but allows us to control *which* specialization the compiler will instantiate in any context. Same here. – Mike Kinghan Feb 13 '17 at 18:40
1

I will sugest to create a helper object which will store the type you want to work with:

template <typename RETURN, typename TYPE, typename CLASS>
struct function_pointer
{ using type_t = RETURN (CLASS::*)(const TYPE &); };

template <typename RETURN, typename TYPE>
struct function_pointer<RETURN, TYPE, std::nullptr_t>
{ using type_t = RETURN (*)(const TYPE &); };

This type will create a member-function-pointer if a class is provided as third parameter and a function-pointer otherwise. Now, we can use this helper in MyClass:

template <typename T, typename CLASS = std::nullptr_t>
class MyClass
{
    using firstFunctionPtr  = typename function_pointer<double, T, CLASS>::type_t;
    using secondFunctionPtr = typename function_pointer<bool, T, CLASS>::type_t;

    // Function pointers as member variables
    firstFunctionPtr _firstFunc;
    secondFunctionPtr _secondFunc;

public:
    inline MyClass(firstFunctionPtr firstFunc, secondFunctionPtr secondFunc) :
        _firstFunc(firstFunc),
        _secondFunc(secondFunc)
    {}

    void call_first(CLASS &c, const T&v) { (c.*_firstFunc)(v); }
    void call_second(CLASS &c, const T&v) { (c.*_secondFunc)(v); }

    void call_first(const T&v) { (_firstFunc)(v); }
    void call_second(const T&v) { (_secondFunc)(v); }
};

I've added call_* functions just to show a use case, which will be as below:

// Some class with the expected function signatures
struct S1
{
    int i = 0;
    double d(const int &) { std::cout << i << ' ' << __PRETTY_FUNCTION__ << '\n'; return{}; }
    bool b(const int &) { std::cout << i << ' ' << __PRETTY_FUNCTION__ << '\n';     return{}; }
};

// Another class with the expected function signatures
struct S2
{
    double d(const int &) { std::cout << __PRETTY_FUNCTION__ << '\n'; return{}; }
    bool b(const int &) { std::cout << __PRETTY_FUNCTION__ << '\n'; return{}; }
};

// Free function with which could have the expected function signature
template <typename R>
R f(const int &) { std::cout << __PRETTY_FUNCTION__ << '\n'; return{}; }

Using MyClass with an arbitrary class (S1):

S1 a{1}, b{2};
S2 c, d;
MyClass<int, S1> MCiS1(&S1::d, &S1::b);
MCiS1.call_first(a, 111);  // Prints -> 1 double S1::d(const int&)
MCiS1.call_second(b, 222); // Prints -> 2 bool S1::b(const int&)
MCiS1.call_first(c, 111);  // Error decltype(c) is not S1.
MCiS1.call_second(d, 222); // Error decltype(d) is not S1.

Using MyClass with a different class (S2):

MyClass<int, S2> MCiS2(&S2::d, &S2::b);
MCiS2.call_first(c, 111);  // Prints -> double S2::d(const int&)
MCiS2.call_second(d, 222); // Prints -> bool S2::b(const int&)
MCiS2.call_first(a, 111);  // Error decltype(c) is not S2.
MCiS2.call_second(b, 222); // Error decltype(d) is not S2.

Using MyClass with non-member functions:

MyClass<int> MCi(f<double>, f<bool>);
MCi.call_first(111);  // Prints -> R f(const int&) [with R = double]
MCi.call_second(222); // Prints -> R f(const int&) [with R = bool]

Check the live demo Here.

PaperBirdMaster
  • 12,806
  • 9
  • 48
  • 94
0

All you need to do is bind the object instance for the member function pointer as a first argument.

struct foo {
    float bar1(const type &var);
    bool bar2(const type &var);
};
foo my_foo;
auto f1 = std::bind(&foo::bar1, my_foo, _1);
auto f2 = std::bind(&foo::bar2, my_foo, _1);
MyClass<type> my_obj(f1, f2);
Paul Evans
  • 27,315
  • 3
  • 37
  • 54
  • But I need the type to be templated, rather than passed by argument, in order to work with my member variables. How would that change your answer – marcman Feb 12 '17 at 00:54
  • Ok, just add that. This answers your OP about making function pointers from member functions. – Paul Evans Feb 12 '17 at 00:57
  • Well wait. So `auto f1 = std::bind(&foo::bar1, my_foo); auto f2 = std::bind(&foo::bar2, my_foo);` should be able to be stored as the member vars I declared in my OP? Or rather, those statements will allow member function pointers to be stored as non-member function pointers? – marcman Feb 12 '17 at 00:58