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: Mon, 19 Aug 2019 17:38:22 +0000
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:
[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]>
Sent: Monday, August 19, 2019 11:43 AM
To: std-discussion_at_[hidden] <std-discussion_at_[hidden]>
Cc: 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

Am Mo., 19. Aug. 2019 um 18:22 Uhr schrieb Princeton Ferro via
Std-Discussion <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

Received on 2019-08-19 12:40:27