C++ Logo

std-discussion

Advanced search

Re: Clarification needed: block scope thread_locals can be referenced for the first time without control passing through declaration

From: Princeton Ferro <pferro_at_[hidden]>
Date: Thu, 22 Aug 2019 13:51:44 +0000
Some very good points raised, but is it not at all worth considering how the user would expect this program to behave? I would argue that if you asked ten developers, at least eight would expect the thread-local variable to be initialized in the lambda. It is certainly possible to come up with a definition that removes all ambiguity on how this could work. For example, specifying that such variables will have their initializer implicitly called on entry into the lambda region, and for multiple such variables their initializers are ordered w.r.t. the order they were declared in the translation unit.

Regardless, the spec itself doesn't offer a satisfyingly clear interpretation either way. It neither says this is undefined behavior nor an error. It really looks as though this was an oversight. For this reason, I will be submitting a defect report.
________________________________
From: Brian Bi <bbi5291_at_[hidden]>
Sent: Monday, August 19, 2019 12:46 PM
To: std-discussion_at_[hidden] <std-discussion_at_[hidden]>
Cc: Daniel Krügler <daniel.kruegler_at_[hidden]>; Princeton Ferro <pferro_at_[hidden]>; pe_cce_openmp <pe_cce_openmp_at_[hidden]>
Subject: Re: [std-discussion] Clarification needed: block scope thread_locals can be referenced for the first time without control passing through declaration

On Mon, Aug 19, 2019 at 12:38 PM Princeton Ferro via Std-Discussion <std-discussion_at_[hidden]<mailto:std-discussion_at_[hidden]>> wrote:
I disagree, because this is not the behaviour function-local variables
of static-storage duration
If we were to go with interpretation #2, then it would effectively mean that you could never use block-scope thread-local variables in a lambda, since (1) the standard says you can only capture variables with automatic storage:

Well, you surely can---you simply cannot execute a lambda like the one in your example in a new thread. And I think that in general the compiler cannot prove that said lambda is or is not executed in a new thread, so this is a quality of implementation issue---if you are lucky, maybe the compiler will catch some cases and warn you, and maybe even tell you the safe alternative---namely, to init-capture the variable by reference.
[expr.prim.lambda.capture - 8.1.5.2, pp 4]
The identifier in a simple-capture is looked up using the usual rules for unqualified name lookup (6.4.1); each
such lookup shall find an entity. An entity that is designated by a simple-capture is said to be explicitly
captured, and shall be *this (when the simple-capture is “this” or “* this”) or a variable with automatic
storage duration declared in the reaching scope of the local lambda expression
And (2) since the use cases of lambdas almost always is to pass them to some non-local function.
Would you agree at least that the standard could be revised to address this issue more clearly?
________________________________
From: Daniel Krügler <daniel.kruegler_at_[hidden]<mailto:daniel.kruegler_at_[hidden]>>
Sent: Monday, August 19, 2019 11:43 AM
To: std-discussion_at_[hidden]<mailto:std-discussion_at_[hidden]> <std-discussion_at_[hidden]<mailto:std-discussion_at_[hidden]>>
Cc: Princeton Ferro <pferro_at_[hidden]<mailto:pferro_at_[hidden]>>; pe_cce_openmp <pe_cce_openmp_at_[hidden]<mailto:pe_cce_openmp_at_[hidden]>>
Subject: Re: [std-discussion] Clarification needed: block scope thread_locals can be referenced for the first time without control passing through declaration

Am Mo., 19. Aug. 2019 um 18:22 Uhr schrieb Princeton Ferro via
Std-Discussion <std-discussion_at_[hidden]<mailto:std-discussion_at_[hidden]>>:
>
> The standard (C++17) says that variables with thread-storage duration must be initialized before their first ODR use:
>
> [basic.std.thread - 6.7.2, pp 2]
> A variable with thread storage duration shall be initialized before its first odr-use (6.2) and, if constructed,
> shall be destroyed on thread exit.
>
>
> Later, the standard says that for thread-local variables with block scope, they must be initialized when control first passes through their declaration:
>
> [stmt.dcl - 9.7, pp 4]
> Dynamic initialization of a block-scope variable with static storage duration (6.7.1) or thread storage
> duration (6.7.2) is performed the first time control passes through its declaration
>
>
> Now consider the following example:
>
> #include <thread>
> #include <iostream>
>
> struct Object {
> int i;
> Object() : i(3) {}
> };
>
> int main(void) {
> static thread_local Object o;
>
> std::cout << "[main] o.i = " << o.i << std::endl;
> std::thread t([] { std::cout << "[new thread] o.i = " << o.i << std::endl; });
> t.join();
> }
>
>
> o is a block-scope variable with thread storage and a dynamic initializer. The lambda passed into std::thread's constructor refers to o but does not capture it, which should be fine since o is not a variable with automatic storage. However, when control passes through the lambda, it will do so on a new thread. When that happens, it will refer to o for the first time. Because o is a thread-local variable, (6.7.2) implies it should be initialized, but because it is declared in block scope, (9.7) implies that it can only be initialized when control passes through its declaration, which will never happen on the new thread.
>
> What should be done? A few options I can think of:
>
> Because it has thread storage, the variable must be initialized by the time it is first used on any thread. Essentially, the treatment should be the same as if o was declared outside the body of main().

I disagree, because this is not the behaviour function-local variables
of static-storage duration

> It is an error to refer to a block-scope thread-local variable within a lambda that escapes the block it was declared in.

Exactly that. To reduce the risk that this happens, a popular
technique is to use Meyer's singleton approach for variables of thread
local storage as well such as in the following example

[..]

Object& get_object()
{
  thread_local Object o;
  return o;
}

int main(void) {
    std::cout << "[main] o.i = " << get_object().i << std::endl;
    std::thread t([] { std::cout << "[new thread] o.i = " <<
get_object().i << std::endl; });
    t.join();
}

- Daniel
--
Std-Discussion mailing list
Std-Discussion_at_[hidden]<mailto:Std-Discussion_at_[hidden]>
https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion
--
Brian Bi

Received on 2019-08-22 08:53:49