18

Why member functions cannot be used as template arguments? For example, I want to do like:

struct Foo {
    void Bar() { // do something
    }
};
template <typename TOwner, void(&func)()>
void Call(TOwner *p) {
    p->func();
}
int main() {
    Foo a;
    Call<Foo, Foo::Bar>(&a);
    return 0;
}

I know that a similar thing can be done using pointers-to-member; well, it's cool enough most of the time, but I'm just curious about why pointers "should" be used.

I see no ambiguity of interpreting "p->func()" above. Why the standard prohibits us to use member functions as template arguments? Even static member functions are not allowed according to my compiler (VC++ 2013). Does anyone know the reason? Or, is there a way to do the same thing without loss of any performance due to pointer dereferencing?

Thank you.

Junekey Jeon
  • 1,496
  • 1
  • 11
  • 18
  • You see no ambiguity because you think template are like macros, but this is not the case, type safety is made by compiler and your expression `p->func()` has no meaning at compile time and `Foo::Bar` is not of type `void(&func)()` but `void(TOwner::*func)()`. – Jean-Baptiste Yunès Jun 19 '15 at 05:35
  • 1
    @Jean-BaptisteYunès Well, Foo::Bar is not of type void(TOwner::*func)(), which is the type of &Foo::Bar. The reason why I wrote void(&func)() is that's the best thing I can think of as the type of Foo::Bar; indeed, the result of decltype(Foo::Bar) is void(void) according to my compiler. What I'm asking is a potential situation of type-safety-breaking when things like above is allowed. Thank you. – Junekey Jeon Jun 19 '15 at 06:23
  • Ok for the reference. I just wanted to point that the problem is that there is some distinction in between : the type (what are the parameters and the return type) and the signature? (context/namespace + type). Alas Foo::Bar signature is not void(void). – Jean-Baptiste Yunès Jun 19 '15 at 07:54
  • The point is : Foo::Bar is a function member, and your template arg is a function but you use it as a function member. – Jean-Baptiste Yunès Jun 19 '15 at 08:03
  • @Jean-BaptisteYunès Yes, and my point is that unfortunately there is neither "member function type" nor "reference to member type" and that's why I've wrote like the above. – Junekey Jeon Jun 20 '15 at 14:36

2 Answers2

37

They can be used as non-type parameters, but you need to use the right syntax

struct Foo {
    void Bar() { // do something
    }
};
template <typename TOwner, void(TOwner::*func)()>
void Call(TOwner *p) {
    (p->*func)();
}
int main() {
    Foo a;
    Call<Foo, &Foo::Bar>(&a);
    return 0;
}
user657267
  • 20,568
  • 5
  • 58
  • 77
3

In fact, member-function pointers can be used as template arguments (just exactly as any other pointer type may be used as template parameter):

struct A
{
    int f(float x);
};

template <int (A::F*)(float)>
struct B {};

template<A *> struct C;
template<A &> struct D;

However, according to the following excerpt from the C++ standard, one cannot pass references to members.

[temp.param]

  1. A non-type template-parameter shall have one of the following (optionally cv-qualified) types:

(4.1) — integral or enumeration type,

(4.2) — pointer to object or pointer to function,

(4.3) — lvalue reference to object or lvalue reference to function,

(4.4) — pointer to member,

(4.5) — std::nullptr_t.



Next, given you managed to pass your function type somehow and want to call it inside, you encounter the same problem as if you want to store them inside a function pointer or a std::function object: namely to call you need both, the member function as well as the concrete object. Passing only the function won't suffice.

But in fact you can achieve what you want. Just bind the function to your object and pass it afterwards:

template<typename T, typename F>
void call(T&& t, F&&f)
{
    f(std::forward<T>(t));
}

struct A
{
    void foo() { std::cout<<"hello"<<std::endl; }  
};

int main()
{
    A a;
    auto f=std::bind(&A::foo, a);   //or possibly "std::ref(a)" instead of "a"
    call(3,f);
}

DEMO

Community
  • 1
  • 1
davidhigh
  • 14,652
  • 2
  • 44
  • 75
  • References to members do not exist. – user657267 Jun 19 '15 at 06:21
  • @user657267: [correct](http://stackoverflow.com/questions/21952386/why-doesnt-reference-to-member-exist-in-c), so you can't use them as template parameters :). Seriously, thanks for noting. – davidhigh Jun 19 '15 at 06:26
  • Thank you for kind explanation. As you have illustrated, I agree that std::bind is a nearly perfect solution, but I think that's not very different from using pointer-to-member; that is, some dynamic overhead should be added if I do that. Of course a concrete object should bind to a member function to be called, but this is not the case for static members. How do you think about that? Also, don't you think "p->" provides such concrete object to bind? I can't imagine a situation where "thinking func() as a member function of p" may cause a problem. – Junekey Jeon Jun 19 '15 at 06:34
  • @JunekeyJeon: for the first: yes, `p->` is that required object, I overlooked that in your code. So, as pointed out by the other answer, it's "just" a syntactical mistake. – davidhigh Jun 19 '15 at 06:38
  • @JunekeyJeon: next, I think that in neither of the two cases involves a "dynamic overhead", because in both the compiler knows which function it has to call. Overhead can occur, however, if you use that together with polymorphism, i.e. pass a base class pointer and call a virtual function (but that is more a general thing and not directly related to your question). Regarding the static member: that should work, yes, but I'm not directly aware how ... I try to avoid all this ugly function pointer stuff and work with the modern C++11 alternatives. – davidhigh Jun 19 '15 at 06:55
  • @davidhigh Dynamic overheads caused by calling virtual functions is not my concern. The reason why I've thought std::bind or function pointer may cause dynamic overhead is that they are "pointers". For "normal" use of pointers (i.e., not as a template parameter but usual variables), referring to the address they points to should produce a dynamic overhead. But to be used as template parameters, a pointer should be a compile-time constant, and I think this can be why there is no overhead at all. Is this right? – Junekey Jeon Jun 20 '15 at 14:34