I'm trying to write a brief unit tester for a project I am working on.
This test passes if the given function throws a certain kind of exception:
template <class Exception, class Return, class... Args>
bool throws(std::string s, Return(*fn)(Args...), Args... args)
{
try
{
fn(args...);
}
catch (Exception& e)
{
return output(s, true);
}
catch (...)
{
return output(s, false);
}
return output(s, false);
}
Using it would look something like this:
throws<int>("Testing: Throws int", &foo);
And that works great. But now I'm trying to write a similar function that will work with member functions of a class.
Here's what I've got so far:
template <class Exception, class Object, class Return, class... Args>
bool throws(std::string s, Object& o, Return(Object::*fn)(Args...), Args... args)
{
// ...
(o.*fn)(args...);
// ...
}
template <class Exception, class Object, class Return, class... Args>
bool throws(std::string s, const Object& o, Return(Object::*fn)(Args...) const, Args... args)
{ /* ... */ }
Which should look something like this when used:
Bar<int> b1(5), b2(2);
typedef Bar<int>& (Bar<int>::*fn)(const Bar<int>&);
typedef Bar<int> (Bar<int>::*const_fn)(const Bar<int>&) const;
fn foo = &Bar<int>::foo;
const_fn const_foo = &Bar<int>::const_foo;
throws<int>("Testing member function", b1, foo, b2);
throws<int>("Testing const member function", b1, const_foo, b2);
But when I do this, I get "no matching function call" for both of these throws() functions. Specifically:
error: no matching function for call to ‘throws(const char [30], Bar<int>&, Bar<int> (Bar<int>::*&)(const Bar<int>&)const, const Bar<int>&)
And a similar one for the non-const version.
I noticed a few consts that were different, so I tried throwing in some const_cast's where I use the function, but no dice. This is the first time I've used member function pointers and variadic templates, (at all, let alone at the same time,) so I definitely could have missed something... anyone more experienced than me have any ideas?
The only thing I haven't been able to account for is the (Bar<int>::*&)
. The & at the end of that doesn't match the call... but that shouldn't be a problem, should it?
EDIT : As requested, my Bar class:
template <class T>
class Bar
{
private:
T _data;
public:
Bar (const int i) : _data(i) {}
Bar const_foo (const Bar& other) const
{
if (_data != other._data)
{
throw _data;
}
return Bar(_data);
}
Bar& foo (const Bar& other)
{
if (_data != other._data)
{
throw _data;
}
return *this;
}
};
And the full errors:
test.cpp: In function ‘int main()’:
test.cpp:111:53: error: no matching function for call to ‘throws(const char [24], Bar<int>&, Bar<int>& (Bar<int>::*&)(const Bar<int>&), Bar<int>&)’
test.cpp:111:53: note: candidates are:
test.cpp:31:6: note: template<class Exception, class Return, class ... Args> bool throws(std::string, Return (*)(Args ...), Args ...)
test.cpp:53:6: note: template<class Exception, class Object, class Return, class ... Args> bool throws(std::string, Object&, Return (Object::*)(Args ...), Args ...)
test.cpp:75:6: note: template<class Exception, class Object, class Return, class ... Args> bool throws(std::string, const Object&, Return (Object::*)(Args ...)const, Args ...)
test.cpp:112:65: error: no matching function for call to ‘throws(const char [30], Bar<int>&, Bar<int> (Bar<int>::*&)(const Bar<int>&)const, Bar<int>&)’
test.cpp:112:65: note: candidates are:
test.cpp:31:6: note: template<class Exception, class Return, class ... Args> bool throws(std::string, Return (*)(Args ...), Args ...)
test.cpp:53:6: note: template<class Exception, class Object, class Return, class ... Args> bool throws(std::string, Object&, Return (Object::*)(Args ...), Args ...)
test.cpp:75:6: note: template<class Exception, class Object, class Return, class ... Args> bool throws(std::string, const Object&, Return (Object::*)(Args ...)const, Args ...)
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.