0

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 ...)
7
  • 1
    A general tip; you could look at std::bind to make the code a bit easier to manage. Commented Apr 30, 2014 at 22:29
  • The compiler can't deduce the type of the function pointer you're passing. Commented Apr 30, 2014 at 22:45
  • @0x499602D2: As far as I can tell, the only difference between the different throws() functions is that the member function ones require a calling object, and the syntax for member function pointers is a bit different. So why did the first example work perfectly? Commented Apr 30, 2014 at 23:17
  • What do you mean why did the first example work? You said "when I do this, I get "no matching function call" for both of these throws() functions" ... Commented Apr 30, 2014 at 23:31
  • 1
    I likely missed something obvious, but is this similar to what you're trying to do? Commented May 1, 2014 at 0:02

2 Answers 2

3

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.

Sign up to request clarification or add additional context in comments.

Comments

1

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.

9 Comments

It is. It would cause a compile error if it didn't match, correct?
I guess so... can you post the definition of Bar class? And the line throws<SizeException>("Testing member function", b1, foo, b2); compiles fine, the problem appears in throws<SizeException>("Testing const member function", b1, const_foo, b2);, is this correct? And don't you get a compile error? At least that's what I understood.
I get the same compile error for both functions. (I should have specified that; I'll edit my original post.) I can post more code if that would help.
Just post the definitions of your functions foo and const_foo inside the Bar<T> class.
What puzzles me is why const char [10] is the first argument of throws in the error, when it should be a std::string. And where did that 10 came from!?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.