TypeIA
TypeIA

Reputation: 17258

Allow template parameter of function pointer type to accept functions of any return type

Is there a way to allow a template parameter of function pointer type to accept a function of any (rather than a specific) return type, when the function's return value is not actually used? Here's an MCVE to illustrate what I mean:

int returnInt(int) { return 0; }
void returnVoid(int) { }

template <int (*Func)(int)>
struct foo { void bar(int x) { Func(x); } };

int main(int, char *[]) {
    foo<returnInt> a; // ok
    foo<returnVoid> b; // argument of type "void (*)(int)" is incompatible
                       // with template parameter of type "int (*)(int)"C/C++(458)
}

I know I can do this, as a workaround:

template <typename ReturnType, ReturnType (*Func)(int)>
struct foo { void bar(int x) { Func(x); } };

int main(int, char *[]) {
    foo<int, returnInt> a; // ok
    foo<void, returnVoid> b; // ok
}

Or even this (which is worse in my opinion, because we lose the type check on the function type's argument types and could fall into SFINAE):

template <typename FuncType, FuncType *Func>
struct foo { void bar(int x) { Func(x); } };

int main(int, char *[]) {
    foo<decltype(returnInt), returnInt> a; // ok
    foo<decltype(returnVoid), returnVoid> b; // ok
}

But I wonder if there's a way to do it without adding an extra template parameter which has no other purpose.

Upvotes: 2

Views: 667

Answers (2)

dfrib
dfrib

Reputation: 73206

I know I can do this, as a workaround:

[...]

But I wonder if there's a way to do it without adding an extra template parameter which has no other purpose.

Although the workaround by OP mentions additional template parameters, they are used as parameters to the primary template, encumbering the client to explicitly specify them in the template argument list when making use of the dispatch API.

The key here, w.r.t. to using additional template parameters, is (as shown in my detailed answer to the related follow-up question) specialization:

template <auto>
struct foo { 
    void bar(int x) = delete; 
};

template <typename T, typename Return, Return (*Func)(T)>
struct foo<Func> {
    void bar(int x) {
        Func(x);
    }
};

int returnInt(int) { return 0; }
void returnVoid(int) { }

int main() {
    foo<returnInt> a;  // OK (GCC and Clang)
    foo<returnVoid> b; // OK (GCC and Clang)
}

The additional template parameters in the partial specialization are not visible to the client, and are entirely deducible from the non-type template parameter associated with the client API.

Upvotes: 1

songyuanyao
songyuanyao

Reputation: 173014

You can declare the template parameter as auto (since C++17) to make it working with any function pointer types.

template <auto Func>
struct foo { void bar(int x) { Func(x); } };

LIVE

And if you want to check the type with SFINAE:

template <auto Func, std::enable_if_t<std::is_function_v<std::remove_pointer_t<decltype(Func)>>> * = nullptr>
struct foo { void bar(int x) { Func(x); } };

LIVE

Or still declare the template parameter as function pointer which might return any types.

template <auto (*Func)(int)>
struct foo { void bar(int x) { Func(x); } };

LIVE

Upvotes: 3

Related Questions