I was reading through the std::thread documentation at cppreference (not always 100% accurate, I know) and noticed the following definition for the behavior of std::thread
when passed a "pointer-to-data-member" (not "pointer-to-member-function") as its first argument (f
) and an object of the required class as its second argument (t1
after copying to thread-local-storage):
If N == 1 and f is pointer to a member data object of a class, then it is accessed. The value of the object is ignored. Effectively, the following code is executed: t1.*f if and the type of t1 is either T, reference to T or reference to type derived from T. (*t1).*f otherwise.
Now, I don't plan on using std::thread
in this way, but I am flummoxed by this definition. Apparently, the only thing that happens is that the data member is accessed and the value ignored, which doesn't seem like it could have any observable side-effects at all, meaning (as far I can tell) it might as well be a no-op. (I might be missing something obvious...?)
At first, I thought this might be a misprint, and meant to say that the data member is accessed and then called (since it might be a callable object, even if it's not a function) but I tested it with the following code in GCC-4.7 and indeed there is no call:
#include <iostream>
#include <thread>
struct S
{
void f() {
std::cout << "Calling f()" << std::endl;
}
struct {
void operator()() {
std::cout << "Calling g()" << std::endl;
}
} g;
};
int main(int, char**)
{
S s;
s.f(); // prints "Calling f()"
s.g(); // prints "Calling g()"
std::cout << "----" << std::endl;
auto x = &S::f; // ptr-to-mem-func
auto y = &S::g; // ptr-to-data-mem
(s.*x)(); // prints "Calling f()"
(s.*y)(); // prints "Calling g()"
std::cout << "----" << std::endl;
std::thread t(x, &s);
t.join();
// "Calling f()" printed by now
std::thread u(y, &s);
u.join();
// "Calling g()" not printed
return 0;
}
Is there any purpose to this definition which doesn't seem to accomplish anything? Why not instead make passing a "pointer-to-data-member-callable" act just like a "pointer-to-member-function", and make passing a "pointer-to-data-member-noncallable" an error? In fact, it seems like this would be the easiest way to implement it, since calling a "pointer-to-data-member-callable" has equivalent syntax to calling as a "pointer-to-member-function" in other contexts (unless there's something in the vagarities of template specialization and SFINAE rules which makes it difficult to treat them equivalently...?)
This is not something I need for actual code, but the fact that this definition exists leaves me suspecting that I am missing something fundamental, which worries me...can anyone enlighten me about this?
This is because of the generic binding facility in terms of which the C++11 Standard defines not only how a thread is started, but also how std::bind
and std::function
work.
In fact, Paragraph 30.3.1.2/3 of the C++11 Standard specifies about the variadic constructor of class std::thread
:
template <class F, class ...Args> explicit thread(F&& f, Args&&... args);
Effects: Constructs an object of type thread. The new thread of execution executes
INVOKE (DECAY_- COPY ( std::forward<F>(f)), DECAY_COPY (std::forward<Args>(args))...)
with the calls toDECAY_COPY
being evaluated in the constructing thread. Any return value from this invocation is ignored. [...]
Ignoring what DECAY_COPY
does (it is not relevant to the question), this is how Paragraph 20.8.2 defines the INVOKE
pseudo-function:
Define INVOKE (f, t1, t2, ..., tN) as follows:
— (t1.*f)(t2, ..., tN) when f is a pointer to a member function of a class T and t1 is an object of type T or a reference to an object of type T or a reference to an object of a type derived from T;
— ((*t1).*f)(t2, ..., tN) when f is a pointer to a member function of a class T and t1 is not one of the types described in the previous item;
— t1.*f when N == 1 and f is a pointer to member data of a class T and t1 is an object of type T or a reference to an object of type T or a reference to an object of a type derived from T;
— (*t1).*f when N == 1 and f is a pointer to member data of a class T and t1 is not one of the types described in the previous item;
— f(t1, t2, ..., tN) in all other cases.
Now the question becomes:
Why does the C++11 Standard defines the
INVOKE
facility that way?
And the answer is here.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With