What do braces on the left-hand side of a variable declaration mean, such as in T {x} = y?
Asked Answered
D

1

51

The code at this GitHub file uses a C++ variable "declaration" syntax I'm not familiar with:

std::unique_ptr<CRecentFileList> {m_pRecentFileList} = std::make_unique<CRecentFileList>(...

(m_pRecentFileList is declarared in a superclass.)

What does it mean when you wrap a variable declaration in braces? (not an initializer list)


I extracted a minimal test case which compiles:

class foo {
    int* p;
    void f(){
        std::unique_ptr<int> {p} = std::make_unique<int>(1);
    }
};

Changing int* p to std::unique_ptr<int> p creates a compilation error due to unique_ptr(const unique_ptr&) = delete;

This makes me think braced declaration assigns to a outer-scope variable with the same name. I tried creating a test program, but it fails to compile:

int main(){
    int x;
    int {x} = 1;
}

error: using temporary as lvalue [-fpermissive]

Dorcus answered 4/7, 2018 at 9:33 Comment(4)
This is still initializer list, no variable is being declared, instead a temporary is created and move assignment operator is invoked. This code seems to be rather pointless.Hattiehatton
Nice try for MCVE with int {x} = 1; BTW. +1.Lepidolite
I've never gotten this many upvotes and responses quickly before... I'll upvote and/or accept answers tomorrow.Dorcus
Coincidentally, you asked on the same day as a similar question.Cameroncameroon
B
47

It's not a declaration. It's an assignment to a temporary.

In std::unique_ptr<int> {p} = std::make_unique<int>(1);, std::unique_ptr<int> {p} creates a unique_ptr temporary that takes ownership of the object p points to, then std::make_unique<int>(1) is assigned to that temporary, which causes the object p points to to be deleted and the temporary to take ownership of the int created by the make_unique; finally, at the ;, the temporary itself is destroyed, deleting the make_unique-created int.

The net result is delete p plus a useless new/delete cycle.

(It would be a declaration had it used parentheses rather than braces: std::unique_ptr<int> (p) = std::make_unique<int>(1); is exactly equivalent to std::unique_ptr<int> p = std::make_unique<int>(1);.)

Bigot answered 4/7, 2018 at 9:37 Comment(7)
What is it used for?Guadalajara
@Ville-Valtteri: Obfuscating delete m_pRecentFileList :-/ (with an useless allocation BTW).Lepidolite
I think I found the reason this function is broken. The original developer thought he found a clever (as he often does) way of constructing a new object. In reality the object/pointer is deleted as soon as it's constructed, causing the app to crash after the function's called (since the pointer's reset to 0xCCCCCCCC).Dorcus
@jimbo1qaz The original pointer (m_pRecentFileList) isn't even updated to point to the temporary object, but one way or another it is indeed "clever" code with undefined behavior.Untrimmed
So looking at earlier questions, this "probably" shouldn't be allowed, but it is because nobody's bothered to sit down and prove that restricting the default to prevent this is a completely safe idea?Gallicism
Correction: Visual Studio debug build resets the deleted pointer to 0xDDDDDDDD. And somehow the original pointer is mutated, not the temporary unique_ptr constructed from it.Dorcus
@Leushenko There is an idiom to call swap on a temporary, e.g. std::vector<X>().swap(myVec); – which predates C++11 and now should be replaced by e.g. move-assignment most of the time – but generally I think you are right: Assigning to a temporary usually indicates a bug. Scott Meyers in his book "Effective C++" (IIRC) recommended returning object types as const to avoid mutating operations on temporaries. In C++11 or higher, this could be better solved by adding lvalue ref-qualifiers on mutating methods. However, it seems the issue was not considered important enough for the std. lib. yetUntrimmed

© 2022 - 2024 — McMap. All rights reserved.