Question

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 ...)
Was it helpful?

Solution

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.

OTHER TIPS

Make sure that Bar<int>::const_foo is a const member function and it has the return type Bar<int> (and not a reference). Post more code if this is not the issue.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top