Arguments passing for std::thread constructor implemented by MSVC

Usually, we will encounter some compiling problems, when implementing cross-platform c++ codes on Visual Studio. Codes compile without errors on windows, while other platform not, this time is about arguments passing of std thread constructor

Issue

Let's check these simple codes

#include <cstdio>
#include <thread>

struct A
{
    int i;
};

void add_one(A & r)
{
    ++r.i;
    printf("now i is %d\n", r.i);
}

int main()
{
    A a{ 9 };
    printf("i is %d\n", a.i);

    std::thread t(add_one, a);
    t.join();

    printf("i after thread: %d", a.i);
    return 0;
}

this program could compile and run on MSVC, we can get the following result:

i is 9
now i is 10
i after thread: 9

Analysis

Obviously, the MSVC implementation passes a copied a to the new-created thread, which is also the reason why a is not modified after threads end. But this program does not compile on g++, because add_one accepts reference arguments, while we passed a value, the corresponding invoke implementation will not be found. Different g++ compiler will show different error for this issue:

  • OSX
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/thread:342:5: error: 
      attempt to use a deleted function
    __invoke(_VSTD::move(_VSTD::get<1>(__t)), _VSTD::move(_VSTD::get<_In...
    ^

This should be caused by matching the null implementation of invoke:

__nat __invoke(__any, ...);
  • Centos 7 gcc 4.8.5
/usr/include/c++/4.8.2/functional:1697:61: error: no type named ‘type’ in ‘class std::result_of<void (*(int))(int&)>’
       typedef typename result_of<_Callable(_Args...)>::type result_type;
                                                             ^
/usr/include/c++/4.8.2/functional:1727:9: error: no type named ‘type’ in ‘class std::result_of<void (*(int))(int&)>’
         _M_invoke(_Index_tuple<_Indices...>)

No type named 'type' in result_type, so gcc does not handle arguments of this type

There's 2 ways to solve this issue:

  1. Change add_one arguments to value, this will output the same result with MSVC
  2. Explicit a as reference: std::thread t(add_one, std::ref(a)); this will output the following result:
i is 9
now i is 10
i after thread: 10

Funny Story

MSVC DID prevent reference arguments, in some way. If we remove the struct to basic type int, the program will be look like this:

#include <cstdio>
#include <thread>

void add_one(int & r)
{
    ++r;
    printf("now i is %d\n", r);
}

int main()
{
    int i = 9;
    printf("i is %d\n", i);

    std::thread t(add_one, i);
    t.join();

    printf("i after thread: %d\n", i);
    return 0;
}

Then MSVC will recognize this:

1>c:\program files (x86)\microsoft visual studio 14.0\vc\include\thr\xthread(240): error C2672: “std::invoke”: 未找到匹配的重载函数

It looks like that MSVC does not implement this strictly, the reason is not known. But what if we pass reference, let's change the codes to:

#include <cstdio>
#include <thread>

struct A
{
    int i;
};

void add_one(A & r)
{
    ++(r.i);
    printf("now i is %d\n", r.i);
}

int main()
{
    A a{ 9 };
    A & r = a;
    printf("i is %d\n", a.i);

    std::thread t(add_one, r);
    t.join();

    printf("i after thread: %d\n", a.i);
    return 0;
}

i is still 9 in the end, and these codes don't compile on g++, by the way, but if we are passing pointers:

#include <cstdio>
#include <thread>

struct A
{
    int i;
};

void add_one(A * r)
{
    ++(r->i);
    printf("now i is %d\n", r->i);
}

int main()
{
    A a{ 9 };
    A * r = &a;
    printf("i is %d\n", a.i);

    std::thread t(add_one, r);
    t.join();

    printf("i after thread: %d\n", a.i);
    return 0;
}

i will be 10 finally, and g++ compiles too, result is also the same.

Conclusion

std::ref must be used if we're running a thread using reference, or unexpected result will be produced on MSVC, though it compiles