First, my code:
#include <iostream>
#include <functional>
#include <string>
#include <thread>
#include <chrono>
using std::string;
using namespace std::chrono_literals;
class MyClass {
public:
MyClass() {}
// More specific constructor.
template< class Function, class... Args >
explicit MyClass( const std::string & theName, Function&& f, Args&&... args )
: name(theName)
{
runner(f, args...);
}
// Less specific constructor
template< class Function, class... Args >
explicit MyClass( Function&& f, Args&&... args ) {
runner(f, args...);
}
void noArgs() { std::cout << "noArgs()...\n"; }
void withArgs(std::string &) { std::cout << "withArgs()...\n"; }
template< class Function, class... Args >
void runner( Function&& f, Args&&... args ) {
auto myFunct = std::bind(f, args...);
std::thread myThread(myFunct);
myThread.detach();
}
std::string name;
};
int main(int, char **) {
MyClass foo;
foo.runner (&MyClass::noArgs, &foo);
foo.runner (&MyClass::withArgs, &foo, std::string{"This is a test"} );
MyClass hasArgs(string{"hasArgs"}, &MyClass::withArgs, foo, std::string{"This is a test"} );
std::this_thread::sleep_for(200ms);
}
I'm trying to build a wrapper around std::thread for (insert lengthy list of reasons). Consider MyClass here to be named ThreadWrapper in my actual library.
I want to be able to construct a MyClass as a direct replacement for std::thread. This means being able to do this:
MyClass hasArgs(&MyClass::withArgs, foo, std::string{"This is a test"} );
But I also want to optionally give threads a name, something like this:
MyClass hasArgs(string{"hasArgs"}, &MyClass::withArgs, foo, std::string{"This is a test"} );
So I created two template constructors. If I only want to do one or the other and only use a single template constructor, what I'm doing is fine.
With the code as written, if you compile (g++), you get nasty errors. If I comment out the more specific constructor, I get a different set of nasty errors. If I comment out the less specific constructor (the one that doesn't have a const std::string & arg), then everything I'm trying to do works. That is, the one with std::string is the right one, and it works.
What's happening is that if I have both constructors, the compiler picks the less specific one each time. I want to force it to use the more specific one. I think I can do this in C++ 17 with traits, but I've never used them, and I wouldn't know where to begin.
For now, I'm going to just use the more specific version (the one that takes a name) and move on. But I'd like to put the less specific one back in and use it when I don't care about the thread names.
But is there some way I can have both templates and have the compiler figure out which one based on whether the first argument is either a std::string or can be turned into one?
No one should spend significant time on this, but if you look at this and say, "Oh, Joe just has to..." then I'd love help. Otherwise I'll just live with this not being 100% a direct drop-in replacement, and that's fine.