I notice you use the non-member version with a function taking zero arguments. I believe your code fails when you try to pass arguments, not when you change it to take a member function. By changing it to take a member function and passing an argument in the same step, you obfuscated the real reason for the failure. I believe your code would work if you tried it with a member function taking zero arguments. And I believe your non-member version would fail if you tried to use it with a function taking one or more arguments.
template <class Exception, class Object, class Return, class... Args>
bool throws(std::string s, Object& o, Return(Object::*fn)(Args...), Args... args)
Here you are telling the compiler that the types of the fourth argument to throws
and the first argument to the function pointer must be exactly the same.
error: no matching function for call to ‘throws(const char [24], Bar<int>&,
Bar<int>& (Bar<int>::*&)(const Bar<int>&), Bar<int>&)’
The compiler is telling you that the fourth argument to throws
and the first argument to the function pointer are different, so it can't find a declaration of throws
which matches. Your template can't match because that would mean Args...
would have to deduce as const Bar<int>&
and Bar<int>&
at the same time, and those are two different types.
The solution provided in a comment by @WhozCraig works because the function type and the argument types are deduced independently. His solution passes the function pointer by universal reference, but that is incidental; it could also take the function pointer by const
lvalue reference, or by value.
template <class Exception, class Func, class... Args>
bool throws(std::string s, Func fn, Args&&... args)
{ /*...*/ }
template <class Exception, class Object, class MemFunc, class... Args>
bool throws(std::string s, Object& o, MemFunc fn, Args&&... args)
{ /*...*/ }
Unfortunately with this approach you may run into situations where the overload is ambiguous. The easy solution to this would be to rename the member version memberThrows
. The more complicated version would be to use tag dispatch or SFINAE (I would prefer the former in this case).
template <class Exception, class Func, class... Args>
bool throwsHelper(std::false_type, std::string s, Func fn, Args&&... args)
{ /*...*/ }
template <class Exception, class MemFunc, class Object, class... Args>
bool throwsHelper(std::true_type, std::string s, MemFunc fn, Object&& o, Args&&... args)
{ /*...*/ }
template <class Exception, class Func, class... Args>
bool throws(std::string s, Func fn, Args&&... args)
{
using isMember = typename std::is_member_pointer<Func>::type;
return throwsHelper(isMember(), s, fn, std::forward<Args>(args)...);
}
Notice that I had to change the order of the arguments to the member version to allow uniform calling in both cases.