C++ Overloaded Function as Template Argument

C++ overloaded function as template argument

You can give an explicit template argument:

bar<int(int)>(3, foo);

or cast the ambiguous function name to a type from which the template argument can be deduced:

bar(3, static_cast<int(*)(int)>(foo));

or wrap it in another function (or function object) to remove the ambiguity

bar(3, [](int x){return foo(x);});

Passing overloaded function and args to template function

The reason for compilation error is that the compiler does not know which fun overload you are actually going to use.

To resolve this error, you just need to cast your function parameter to the right overload like:

int main()
{
call( static_cast< void(*)(int, int, int) >( fun ), 1, 2, 3 );

call( static_cast< void(*)(Demo&&, Demo&&) >( fun ), Demo{}, Demo{} );

return 0;
}

FYI, what your call function is trying to do is actually defined by the standard. It is std::invoke function and it comes with C++17 standard.

How to declare the template argument for an overloaded function

You can wrap the function in a lambda, or pass a function pointer after casting it to the type of the overload you want to call or explicitly specify the template parameter:

use_funct([](){ do_something (); });
use_funct(static_cast<void(*)()>(do_something));
use_funct<void()>(do_something);

Wrapping it in a lambda has the advantage, that it is possible to defer overload resolution to use_func. For example:

void do_something(int) {}

void do_something(double) {}

template<typename F> void use_funct(F funct) {
funct(1); // calls do_something(int)
funct(1.0); // calls do_something(double)
}

int main() {
use_funct([](auto x){ do_something (x); });
}

[...] possibly without referring to a function pointer?

I am not sure what you mean or why you want to avoid that. void() is the type of the function, not a function pointer. If you care about spelling out the type, you can use an alias:

using func_type = void();
use_funct<func_type>(do_something);

Overloaded function templates that differ only in their return types in C++

Why does the language make such exception for the templates?

You mean this?

signature [defns.signature.templ]

⟨function template⟩ name, parameter-type-list, enclosing namespace (if any), return type, template-head, and trailing requires-clause (if any)

Yes, the return type is there. It's what always made possible things like

template<typename T>
typename std::enable_if<std::is_integral<T>::value>::type foo(T&);

template<typename T>
typename std::enable_if<!std::is_integral<T>::value>::type foo(T&);

SFINAE is why the return type is included in the signature. Substitution failure is possible in the return type, so it's part of signature comparison. You can still potentially generate two conflicting specializations to overload on (in the general case, not in the example), but the templates would be different.

Overload function template using a templated type

You have to write your own concept.

Adopted checking whether a class is a specialization from Check if class is a template specialization? and added a requires-clause to only accept the standard allocator.

template <class T, template <class...> class TT>
struct is_specialization : std::false_type {};

template <template <class...> class TT, class... Ts>
struct is_specialization<TT<Ts...>, TT> : std::true_type {};

template <class T, template <class...> class TT>
concept specializes = is_specialization<T, TT>::value;

template <typename T, specializes<std::vector> F>
T function(const F& f) const requires specializes<F::allocator_type, std::allocator>;

Unless the template-arguments may be provided manually, overload-resolution should work with the following overload though:

template <typename T, class V>
T function(const std::vector<V>& f) const;

Why does template argument deduction fail with overloaded function?

The problem here is that there is more than one function that works. The deduction of Params... is going to happen before you even get to the SFINAE part of the template. When it tries to deduce Params.. from void (*fn)(Params...) it matches void foo(int, int) and void foo(std::string, std::string). Since it finds multiple matches, 17.8.2.1.6.2 states that it is treated as a non deduced context.

Since it cant deduce a type, you get hard stop error. SFINAE only happens after the template parameter deduction step, which it can't get to in this case.

Overload function/method with template argument

(The comments on the question raise some valid questions about why you would have this. But just focusing on the question as asked.)

It is a bit hacky, but sometimes having an API function call an implementation function for technical reasons is a reasonable approach. One gotcha you might get into with that pattern is that pointers can automatically convert to other pointers: particularly T* to const T* and Derived* to Base*.

A rewrite using if constexpr (C++17 or later):

template <typename T>
T C<T>::f() const {
if constexpr (std::is_same_v<T, int>) {
return 42;
} else if constexpr (std::is_same_v<T, std::string>) {
// Note I changed the return type to T. If it's auto,
// this would need return std::string{"hello"} so you don't
// just return a const char*.
return "hello";
} else if constexpr (std::is_same_v<T, std::vector<char>>) {
return {'h', 'i'};
} else {
// Always false (but don't let the compiler "know" that):
static_assert(!std::is_same_v<T,T>,
"Invalid type T for C<T>::f");
}
}

No more worries about pointer conversions, and this gives you a nicer error message if T is an unhandled type.

A rewrite using constraints (C++20 or later):

template<typename T>
class C {
public:
int f() const requires std::is_same_v<T,int> {
return 42;
}
std::string f() const requires std::is_same_v<T,std::string> {
return "hello";
}
std::vector<char> f() const
requires std::is_same_v<T, std::vector<char>>
{
return {'h', 'i'};
}
};

Now if a bad type T is used, the class doesn't contain any member named f at all! That could be better for other templates which might try checking if x.f() is a valid expression. And if a bad type is used, the compiler error might list the overloads which were declared but discarded from the class.

C++ function template overload on template parameter

Default arguments don't play a role in determining uniqueness of functions. So what the compiler sees is that you're defining two functions like:

template <class T, class>
void fn(T t) { }

template <class T, class>
void fn(T t) { }

That's redefining the same function, hence the error. What you can do instead is make the enable_if itself a template non-type parameter:

template <class T, std::enable_if_t<std::is_arithmetic<T>::value, int> = 0>
void fn(T t) { }

template <class T, std::enable_if_t<!std::is_arithmetic<T>::value, int> = 0>
void fn(T t) { }

And now we have different signatures, hence different functions. SFINAE will take care of removing one or the other from the overload set as expected.



Related Topics



Leave a reply



Submit