Problem
I am trying to pass a lambda-closure to std::thread
that calls arbitrary closed-over function with arbitrary closed-over arguments.
template< class Function, class... Args >
std::thread timed_thread(Function&& f, Args&&... args) {
// Regarding capturing perfectly-forwarded variables in lambda, see [1]
auto thread_thunk = ([&] {
std::cout << "Start thread timer" << std::endl;
// Regarding std::invoke(_decay_copy(...), ...), see (3) of [2].
// Assume no exception can be thrown from copying.
std::invoke(_decay_copy(std::forward<Function>(f)),
_decay_copy(std::forward<Args>(args)...));
}
}
int main() {
int i = 3;
std::thread t = timed_thread(&print_int_ref, std::ref(i));
t.join()
return 0;
}
/*
[1]: https://stackoverflow.com/questions/26831382/capturing-perfectly-forwarded-variable-in-lambda
[2]: https://en.cppreference.com/w/cpp/thread/thread/thread
*/
- I use
std::forward
so that r-value references and l-value references get forwarded (dispatched correctly). - Because
std::invoke
and the lambda create temporary data-structures, the caller must wrap references instd::ref
.
The code appears to work, but causes stack-use-after-scope
with address sanitization. This is my primary confusion.
Suspects
I think this may be related to this error, but I do not see the relation since I am not returning a reference; The reference to i
should be valid for the duration of main
's stack-frame which should outlast the thread because main
joins on it. The reference is passed by copies (std::reference_wrapper
) into the thread_thunk
.
I suspect args...
cannot be captured by reference, but then how should it be captured?
A secondary confusion: changing {std::thread t = timed_thread(blah); t.join();}
(braces to force destructor) to timed_thread(blah).join();
incurs no such problem, even though to me they appear equivalent.
Minimal example
#include <functional>
#include <iostream>
#include <thread>
template <class T>
std::decay_t<T> _decay_copy(T&& v) { return std::forward<T>(v); }
template< class Function, class... Args >
std::thread timed_thread(Function&& f, Args&&... args) {
// Regarding capturing perfectly-forwarded variables in lambda, see [1]
auto thread_thunk = ([&] {
std::cout << "Start thread timer" << std::endl;
// Regarding std::invoke(_decay_copy(...), ...), see (3) of [2].
// Assume no exception can be thrown from copying.
std::invoke(_decay_copy(std::forward<Function>(f)),
_decay_copy(std::forward<Args>(args)...));
std::cout << "End thread timer" << std::endl;
});
/* The single-threaded version code works perfectly */
// thread_thunk();
// return std::thread{[]{}};
/* multithreaded version appears to work
but triggers "stack-use-after-scope" with ASAN */
return std::thread{thread_thunk};
}
void print_int_ref(int& i) { std::cout << i << std::endl; }
int main() {
int i = 3;
/* This code appears to work
but triggers "stack-use-after-scope" with ASAN */
// {
// std::thread t = timed_thread(&print_int_ref, std::ref(i));
// t.join();
// }
/* This code works perfectly */
timed_thread(&print_int_ref, std::ref(i)).join();
return 0;
}
Compiler command: clang++ -pthread -std=c++17 -Wall -Wextra -fsanitize=address test.cpp && ./a.out
. Remvoe address
to see it work.