I've seen this problem on multiple occasions, and it seems it occurs in both Windoes(visual studio) and Linux(gcc). Here is a streamlined version of it:
class noncopyable
{
public:
noncopyable(int n);
~noncopyable();
noncopyable(const noncopyable&) = delete;
noncopyable& operator=(const noncopyable&) = delete;
noncopyable(noncopyable&&);
int& setvalue();
private:
int* number;
};
class thread_starter
{
public:
template<typename callable>
bool start(callable & o);
};
template<typename callable>
inline bool thread_starter::start(callable & o)
{
std::thread t(
[&]() {
int i = 10;
while (i-- > 0)
{
noncopyable m(i);
std::thread child(o, std::move(m));
child.detach();
}
});
return true;
}
class callable
{
public:
virtual void operator()(noncopyable &m);
};
void callable::operator()(noncopyable & m) { m.setvalue()++; }
int main()
{
thread_starter ts;
callable o;
ts.start(o);
}
The code seems legit enough, but It won't compile.
In Visual Studio, it will give:
error C2893: Failed to specialize function template 'unknown-type std::invoke(_Callable &&,_Types &&...) noexcept(<expr>)'
in GCC, it will give:
error: no type named ‘type’ in ‘class std::result_of<callable(int)>’....
I think I know the problem is in some form of copying or referencing mechanism, but all the syntax seems to be proper.
What am I missing?
I've changed the example a little bit and I apologize for the confusion. I'm trying to recreate the problem as pure as possible, and I don't fully understand it myself.
I made a similar program to figure out what happens. This is how it looks:
class mynoncopy
{
public:
mynoncopy(int resource)
: resource(resource)
{
}
mynoncopy(mynoncopy&& other)
: resource(other.resource)
{
other.resource = 0;
}
mynoncopy(const mynoncopy& other) = delete;
mynoncopy& operator =(const mynoncopy& other) = delete;
public:
void useResource() {}
private:
int resource;
};
class mycallablevaluearg
{
public:
void operator ()(mynoncopy noncopyablething)
{
noncopyablething.useResource();
}
};
class mycallableconstrefarg
{
public:
void operator ()(const mynoncopy& noncopyablething)
{
//noncopyablething.useResource(); // can't do this becuase of const :(
}
};
class mycallablerefarg
{
public:
void operator ()(mynoncopy& noncopyablething)
{
noncopyablething.useResource();
}
};
class mycallablervaluerefarg
{
public:
void operator ()(mynoncopy&& noncopyablething)
{
noncopyablething.useResource();
}
};
class mycallabletemplatearg
{
public:
template<typename T>
void operator ()(T&& noncopyablething)
{
noncopyablething.useResource();
}
};
When you issue std::thread(callable, std::move(thenoncopyableinstance))
these two things will happen internally using template magic:
A tuple is created with your callable and all the args.std::tuple<mycallablerefarg, mynoncopy> thetuple(callable,
std::move(thenoncopyableinstance));
The callable will be copied in this case.
std::invoke()
is used to invoke the callable and the arg is passed to it from the tuple using move semantics.std::invoke(std::move(std::get<0>(thetuple)), std::move(std::get<1>(thetuple)));
Because move semantics is used it will expect the callable to receive a rvalue reference as an argument (mynoncopy&&
in our case). This limits us to the following argument signatures:
mynoncopy&&
const mynoncopy&
T&&
where T is a template argumentmynoncopy
not a reference (this will call the move-constructor)These are the compilation results of using the different types of callables:
mynoncopy testthing(1337);
std::thread t(mycallablerefarg(), std::move(testthing)); // Fails, because it can not match the arguments. This is your case.
std::thread t(mycallablevaluearg(), std::move(testthing)); // OK, because the move semantics will be used to construct it so it will basically work as your solution
std::thread t(mycallableconstrefarg(), std::move(testthing)); // OK, because the argument is const reference
std::thread t(mycallablervaluerefarg(), std::move(testthing)); // OK, because the argument is rvalue reference
std::thread t(mycallabletemplatearg(), std::move(testthing)); // OK, because template deduction kicks in and gives you noncopyablething&&
std::thread t(std::bind(mycallablerefarg(), std::move(testthing))); // OK, gives you a little bit of call overhead but works. Because bind() does not seem to use move semantics when invoking the callable
std::thread t(std::bind(mycallablevalue(), std::move(testthing))); // Fails, because bind() does not use move semantics when it invokes the callable so it will need to copy the value, which it can't.