C++ Compiler allows circular definition?
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty{ height:90px;width:728px;box-sizing:border-box;
}
I ran into the following oddity when making a mistake writing some code for trees. I've stripped down this example a lot so it is only a Linear Tree.
Basically, in the main() function, I wanted to attach a Node to my tree, but instead of attaching it to "tree.root", I attached it to just "root". However, to my surprise, not only did it all compile just fine, but I was able to call methods on the nodes. It only errored when I tried to access the "value" member variable.
I guess my main question is, why didn't the compiler catch this bug?
std::shared_ptr<Node> root = tree.AddLeaf(12, root);
Since "root" on the RHS is a flat-out undeclared variable. Also, out of curiosity, if the compiler lets them through, do circular definitions have an actual use case? Here's the rest of the code:
#include <iostream>
#include <memory>
struct Node
{
int value;
std::shared_ptr<Node> child;
Node(int value)
: value {value}, child {nullptr} {}
int SubtreeDepth()
{
int current_depth = 1;
if(child != nullptr) return current_depth + child->SubtreeDepth();
return current_depth;
}
};
struct Tree
{
std::shared_ptr<Node> root;
std::shared_ptr<Node> AddLeaf(int value, std::shared_ptr<Node>& ptr)
{
if(ptr == nullptr)
{
ptr = std::move(std::make_shared<Node>(value));
return ptr;
}
else
{
std::shared_ptr<Node> newLeaf = std::make_shared<Node>(value);
ptr->child = std::move(newLeaf);
return ptr->child;
}
}
};
int main(int argc, char * argv)
{
Tree tree;
std::shared_ptr<Node> root = tree.AddLeaf(12, root);
std::shared_ptr<Node> child = tree.AddLeaf(16, root);
std::cout << "root->SubtreeDepth() = " << root->SubtreeDepth() << std::endl;
std::cout << "child->SubtreeDepth() = " << child->SubtreeDepth() << std::endl;
return 0;
}
Output:
root->SubtreeDepth() = 2
child->SubtreeDepth() = 1
c++ c++14
add a comment |
I ran into the following oddity when making a mistake writing some code for trees. I've stripped down this example a lot so it is only a Linear Tree.
Basically, in the main() function, I wanted to attach a Node to my tree, but instead of attaching it to "tree.root", I attached it to just "root". However, to my surprise, not only did it all compile just fine, but I was able to call methods on the nodes. It only errored when I tried to access the "value" member variable.
I guess my main question is, why didn't the compiler catch this bug?
std::shared_ptr<Node> root = tree.AddLeaf(12, root);
Since "root" on the RHS is a flat-out undeclared variable. Also, out of curiosity, if the compiler lets them through, do circular definitions have an actual use case? Here's the rest of the code:
#include <iostream>
#include <memory>
struct Node
{
int value;
std::shared_ptr<Node> child;
Node(int value)
: value {value}, child {nullptr} {}
int SubtreeDepth()
{
int current_depth = 1;
if(child != nullptr) return current_depth + child->SubtreeDepth();
return current_depth;
}
};
struct Tree
{
std::shared_ptr<Node> root;
std::shared_ptr<Node> AddLeaf(int value, std::shared_ptr<Node>& ptr)
{
if(ptr == nullptr)
{
ptr = std::move(std::make_shared<Node>(value));
return ptr;
}
else
{
std::shared_ptr<Node> newLeaf = std::make_shared<Node>(value);
ptr->child = std::move(newLeaf);
return ptr->child;
}
}
};
int main(int argc, char * argv)
{
Tree tree;
std::shared_ptr<Node> root = tree.AddLeaf(12, root);
std::shared_ptr<Node> child = tree.AddLeaf(16, root);
std::cout << "root->SubtreeDepth() = " << root->SubtreeDepth() << std::endl;
std::cout << "child->SubtreeDepth() = " << child->SubtreeDepth() << std::endl;
return 0;
}
Output:
root->SubtreeDepth() = 2
child->SubtreeDepth() = 1
c++ c++14
7
int x = x + 1;
what bug? What did you expect to happen?
– Kamil Cuk
Jan 7 at 11:00
First the variable is initialized (int std::shared_ptr's case with null pointer), then the assignment is performed. Try using a constructor and it will not be allowed.
– rkapl
Jan 7 at 11:02
2
@rkapl no, the variable is not initialized in this case until RHS completes. This statement is known as copy initialization.
– Ruslan
Jan 7 at 21:39
@Ruslan -- you are right, thanks.
– rkapl
Jan 8 at 21:06
add a comment |
I ran into the following oddity when making a mistake writing some code for trees. I've stripped down this example a lot so it is only a Linear Tree.
Basically, in the main() function, I wanted to attach a Node to my tree, but instead of attaching it to "tree.root", I attached it to just "root". However, to my surprise, not only did it all compile just fine, but I was able to call methods on the nodes. It only errored when I tried to access the "value" member variable.
I guess my main question is, why didn't the compiler catch this bug?
std::shared_ptr<Node> root = tree.AddLeaf(12, root);
Since "root" on the RHS is a flat-out undeclared variable. Also, out of curiosity, if the compiler lets them through, do circular definitions have an actual use case? Here's the rest of the code:
#include <iostream>
#include <memory>
struct Node
{
int value;
std::shared_ptr<Node> child;
Node(int value)
: value {value}, child {nullptr} {}
int SubtreeDepth()
{
int current_depth = 1;
if(child != nullptr) return current_depth + child->SubtreeDepth();
return current_depth;
}
};
struct Tree
{
std::shared_ptr<Node> root;
std::shared_ptr<Node> AddLeaf(int value, std::shared_ptr<Node>& ptr)
{
if(ptr == nullptr)
{
ptr = std::move(std::make_shared<Node>(value));
return ptr;
}
else
{
std::shared_ptr<Node> newLeaf = std::make_shared<Node>(value);
ptr->child = std::move(newLeaf);
return ptr->child;
}
}
};
int main(int argc, char * argv)
{
Tree tree;
std::shared_ptr<Node> root = tree.AddLeaf(12, root);
std::shared_ptr<Node> child = tree.AddLeaf(16, root);
std::cout << "root->SubtreeDepth() = " << root->SubtreeDepth() << std::endl;
std::cout << "child->SubtreeDepth() = " << child->SubtreeDepth() << std::endl;
return 0;
}
Output:
root->SubtreeDepth() = 2
child->SubtreeDepth() = 1
c++ c++14
I ran into the following oddity when making a mistake writing some code for trees. I've stripped down this example a lot so it is only a Linear Tree.
Basically, in the main() function, I wanted to attach a Node to my tree, but instead of attaching it to "tree.root", I attached it to just "root". However, to my surprise, not only did it all compile just fine, but I was able to call methods on the nodes. It only errored when I tried to access the "value" member variable.
I guess my main question is, why didn't the compiler catch this bug?
std::shared_ptr<Node> root = tree.AddLeaf(12, root);
Since "root" on the RHS is a flat-out undeclared variable. Also, out of curiosity, if the compiler lets them through, do circular definitions have an actual use case? Here's the rest of the code:
#include <iostream>
#include <memory>
struct Node
{
int value;
std::shared_ptr<Node> child;
Node(int value)
: value {value}, child {nullptr} {}
int SubtreeDepth()
{
int current_depth = 1;
if(child != nullptr) return current_depth + child->SubtreeDepth();
return current_depth;
}
};
struct Tree
{
std::shared_ptr<Node> root;
std::shared_ptr<Node> AddLeaf(int value, std::shared_ptr<Node>& ptr)
{
if(ptr == nullptr)
{
ptr = std::move(std::make_shared<Node>(value));
return ptr;
}
else
{
std::shared_ptr<Node> newLeaf = std::make_shared<Node>(value);
ptr->child = std::move(newLeaf);
return ptr->child;
}
}
};
int main(int argc, char * argv)
{
Tree tree;
std::shared_ptr<Node> root = tree.AddLeaf(12, root);
std::shared_ptr<Node> child = tree.AddLeaf(16, root);
std::cout << "root->SubtreeDepth() = " << root->SubtreeDepth() << std::endl;
std::cout << "child->SubtreeDepth() = " << child->SubtreeDepth() << std::endl;
return 0;
}
Output:
root->SubtreeDepth() = 2
child->SubtreeDepth() = 1
c++ c++14
c++ c++14
asked Jan 7 at 10:57
user2520385user2520385
848616
848616
7
int x = x + 1;
what bug? What did you expect to happen?
– Kamil Cuk
Jan 7 at 11:00
First the variable is initialized (int std::shared_ptr's case with null pointer), then the assignment is performed. Try using a constructor and it will not be allowed.
– rkapl
Jan 7 at 11:02
2
@rkapl no, the variable is not initialized in this case until RHS completes. This statement is known as copy initialization.
– Ruslan
Jan 7 at 21:39
@Ruslan -- you are right, thanks.
– rkapl
Jan 8 at 21:06
add a comment |
7
int x = x + 1;
what bug? What did you expect to happen?
– Kamil Cuk
Jan 7 at 11:00
First the variable is initialized (int std::shared_ptr's case with null pointer), then the assignment is performed. Try using a constructor and it will not be allowed.
– rkapl
Jan 7 at 11:02
2
@rkapl no, the variable is not initialized in this case until RHS completes. This statement is known as copy initialization.
– Ruslan
Jan 7 at 21:39
@Ruslan -- you are right, thanks.
– rkapl
Jan 8 at 21:06
7
7
int x = x + 1;
what bug? What did you expect to happen?– Kamil Cuk
Jan 7 at 11:00
int x = x + 1;
what bug? What did you expect to happen?– Kamil Cuk
Jan 7 at 11:00
First the variable is initialized (int std::shared_ptr's case with null pointer), then the assignment is performed. Try using a constructor and it will not be allowed.
– rkapl
Jan 7 at 11:02
First the variable is initialized (int std::shared_ptr's case with null pointer), then the assignment is performed. Try using a constructor and it will not be allowed.
– rkapl
Jan 7 at 11:02
2
2
@rkapl no, the variable is not initialized in this case until RHS completes. This statement is known as copy initialization.
– Ruslan
Jan 7 at 21:39
@rkapl no, the variable is not initialized in this case until RHS completes. This statement is known as copy initialization.
– Ruslan
Jan 7 at 21:39
@Ruslan -- you are right, thanks.
– rkapl
Jan 8 at 21:06
@Ruslan -- you are right, thanks.
– rkapl
Jan 8 at 21:06
add a comment |
2 Answers
2
active
oldest
votes
That's an unfortunate side-effect of definitions in C++, that declaration and definition is done as separate steps. Because the variables are declared first, they can be used in their own initialization:
std::shared_ptr<Node> root = tree.AddLeaf(12, root);
^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^
Declaration of the variable Initialization clause of variable
Once the variable is declared, it can be used in the initialization for the full definition of itself.
It will lead to undefined behavior in AddLeaf
if the data of the second argument is used, as the variable is not initialized.
In this case - the default constructor of root would be called first; then addLeaf; then equals operator. I don't think there's any UB here.
– UKMonkey
Jan 7 at 12:44
8
@UKMonkey Note that this isn't assignment, but copy-construction (or rather copy initialization). The definition is equal tostd::shared_ptr<Node> root(tree.AddLeaf(12, root));
The default constructor will not be called.
– Some programmer dude
Jan 7 at 12:48
4
@UKMonkey: Compilers have no choice in this matter. The Standard strictly defines overload resolution for constructors, and the number of arguments alone rules out the default constructor. Efficiency is not a factor in that.
– MSalters
Jan 7 at 13:40
7
It only leads to undefined behavior if the variable is read from or written to. If its address or identity is stored but the value of the variable is never read from or written to, no UB occurs. It is, however, very fragile.
– Yakk - Adam Nevraumont
Jan 7 at 15:29
3
At least in C (can't think of any good reason in c++ though except the original compatibility with c) there's good reason for having definition and declaration separately though:Foo *foo = malloc(sizeof(*foo));
is a common idiom and avoids common pitfalls when refactoring code.
– Voo
Jan 7 at 20:51
|
show 3 more comments
Since "root" on the RHS is a flat-out undeclared variable.
It's not undeclared. It is declared by that very same statement. However, root
is uninitialised at the point where AddLeaf(root)
is called, so when the value of the object is used (compared to null etc.) within the function, the behaviour is undefined.
Yes, using a variable in its own declaration is allowed, but using its value is not. Pretty much all you can do with it is take the address or create a reference, or expressions that only deal with type of the sub expression such as sizeof
and alignof
.
Yes, there are use cases although they may be rare. For example, you might want to represent a graph, and you might have a constructor for node that takes a pointer to linked node as an argument and you might want to be able to represent a node that links with itself. Thus you might write Node n(&n)
. I won't argue whether that would be a good design for a graph API.
Recursive lambda is more reasonable use case: coliru.stacked-crooked.com/a/1c4693c645d76d34
– R2RT
Jan 17 at 11:52
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54073039%2fc-compiler-allows-circular-definition%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
That's an unfortunate side-effect of definitions in C++, that declaration and definition is done as separate steps. Because the variables are declared first, they can be used in their own initialization:
std::shared_ptr<Node> root = tree.AddLeaf(12, root);
^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^
Declaration of the variable Initialization clause of variable
Once the variable is declared, it can be used in the initialization for the full definition of itself.
It will lead to undefined behavior in AddLeaf
if the data of the second argument is used, as the variable is not initialized.
In this case - the default constructor of root would be called first; then addLeaf; then equals operator. I don't think there's any UB here.
– UKMonkey
Jan 7 at 12:44
8
@UKMonkey Note that this isn't assignment, but copy-construction (or rather copy initialization). The definition is equal tostd::shared_ptr<Node> root(tree.AddLeaf(12, root));
The default constructor will not be called.
– Some programmer dude
Jan 7 at 12:48
4
@UKMonkey: Compilers have no choice in this matter. The Standard strictly defines overload resolution for constructors, and the number of arguments alone rules out the default constructor. Efficiency is not a factor in that.
– MSalters
Jan 7 at 13:40
7
It only leads to undefined behavior if the variable is read from or written to. If its address or identity is stored but the value of the variable is never read from or written to, no UB occurs. It is, however, very fragile.
– Yakk - Adam Nevraumont
Jan 7 at 15:29
3
At least in C (can't think of any good reason in c++ though except the original compatibility with c) there's good reason for having definition and declaration separately though:Foo *foo = malloc(sizeof(*foo));
is a common idiom and avoids common pitfalls when refactoring code.
– Voo
Jan 7 at 20:51
|
show 3 more comments
That's an unfortunate side-effect of definitions in C++, that declaration and definition is done as separate steps. Because the variables are declared first, they can be used in their own initialization:
std::shared_ptr<Node> root = tree.AddLeaf(12, root);
^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^
Declaration of the variable Initialization clause of variable
Once the variable is declared, it can be used in the initialization for the full definition of itself.
It will lead to undefined behavior in AddLeaf
if the data of the second argument is used, as the variable is not initialized.
In this case - the default constructor of root would be called first; then addLeaf; then equals operator. I don't think there's any UB here.
– UKMonkey
Jan 7 at 12:44
8
@UKMonkey Note that this isn't assignment, but copy-construction (or rather copy initialization). The definition is equal tostd::shared_ptr<Node> root(tree.AddLeaf(12, root));
The default constructor will not be called.
– Some programmer dude
Jan 7 at 12:48
4
@UKMonkey: Compilers have no choice in this matter. The Standard strictly defines overload resolution for constructors, and the number of arguments alone rules out the default constructor. Efficiency is not a factor in that.
– MSalters
Jan 7 at 13:40
7
It only leads to undefined behavior if the variable is read from or written to. If its address or identity is stored but the value of the variable is never read from or written to, no UB occurs. It is, however, very fragile.
– Yakk - Adam Nevraumont
Jan 7 at 15:29
3
At least in C (can't think of any good reason in c++ though except the original compatibility with c) there's good reason for having definition and declaration separately though:Foo *foo = malloc(sizeof(*foo));
is a common idiom and avoids common pitfalls when refactoring code.
– Voo
Jan 7 at 20:51
|
show 3 more comments
That's an unfortunate side-effect of definitions in C++, that declaration and definition is done as separate steps. Because the variables are declared first, they can be used in their own initialization:
std::shared_ptr<Node> root = tree.AddLeaf(12, root);
^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^
Declaration of the variable Initialization clause of variable
Once the variable is declared, it can be used in the initialization for the full definition of itself.
It will lead to undefined behavior in AddLeaf
if the data of the second argument is used, as the variable is not initialized.
That's an unfortunate side-effect of definitions in C++, that declaration and definition is done as separate steps. Because the variables are declared first, they can be used in their own initialization:
std::shared_ptr<Node> root = tree.AddLeaf(12, root);
^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^
Declaration of the variable Initialization clause of variable
Once the variable is declared, it can be used in the initialization for the full definition of itself.
It will lead to undefined behavior in AddLeaf
if the data of the second argument is used, as the variable is not initialized.
edited Jan 7 at 15:38
answered Jan 7 at 11:03
Some programmer dudeSome programmer dude
305k25265427
305k25265427
In this case - the default constructor of root would be called first; then addLeaf; then equals operator. I don't think there's any UB here.
– UKMonkey
Jan 7 at 12:44
8
@UKMonkey Note that this isn't assignment, but copy-construction (or rather copy initialization). The definition is equal tostd::shared_ptr<Node> root(tree.AddLeaf(12, root));
The default constructor will not be called.
– Some programmer dude
Jan 7 at 12:48
4
@UKMonkey: Compilers have no choice in this matter. The Standard strictly defines overload resolution for constructors, and the number of arguments alone rules out the default constructor. Efficiency is not a factor in that.
– MSalters
Jan 7 at 13:40
7
It only leads to undefined behavior if the variable is read from or written to. If its address or identity is stored but the value of the variable is never read from or written to, no UB occurs. It is, however, very fragile.
– Yakk - Adam Nevraumont
Jan 7 at 15:29
3
At least in C (can't think of any good reason in c++ though except the original compatibility with c) there's good reason for having definition and declaration separately though:Foo *foo = malloc(sizeof(*foo));
is a common idiom and avoids common pitfalls when refactoring code.
– Voo
Jan 7 at 20:51
|
show 3 more comments
In this case - the default constructor of root would be called first; then addLeaf; then equals operator. I don't think there's any UB here.
– UKMonkey
Jan 7 at 12:44
8
@UKMonkey Note that this isn't assignment, but copy-construction (or rather copy initialization). The definition is equal tostd::shared_ptr<Node> root(tree.AddLeaf(12, root));
The default constructor will not be called.
– Some programmer dude
Jan 7 at 12:48
4
@UKMonkey: Compilers have no choice in this matter. The Standard strictly defines overload resolution for constructors, and the number of arguments alone rules out the default constructor. Efficiency is not a factor in that.
– MSalters
Jan 7 at 13:40
7
It only leads to undefined behavior if the variable is read from or written to. If its address or identity is stored but the value of the variable is never read from or written to, no UB occurs. It is, however, very fragile.
– Yakk - Adam Nevraumont
Jan 7 at 15:29
3
At least in C (can't think of any good reason in c++ though except the original compatibility with c) there's good reason for having definition and declaration separately though:Foo *foo = malloc(sizeof(*foo));
is a common idiom and avoids common pitfalls when refactoring code.
– Voo
Jan 7 at 20:51
In this case - the default constructor of root would be called first; then addLeaf; then equals operator. I don't think there's any UB here.
– UKMonkey
Jan 7 at 12:44
In this case - the default constructor of root would be called first; then addLeaf; then equals operator. I don't think there's any UB here.
– UKMonkey
Jan 7 at 12:44
8
8
@UKMonkey Note that this isn't assignment, but copy-construction (or rather copy initialization). The definition is equal to
std::shared_ptr<Node> root(tree.AddLeaf(12, root));
The default constructor will not be called.– Some programmer dude
Jan 7 at 12:48
@UKMonkey Note that this isn't assignment, but copy-construction (or rather copy initialization). The definition is equal to
std::shared_ptr<Node> root(tree.AddLeaf(12, root));
The default constructor will not be called.– Some programmer dude
Jan 7 at 12:48
4
4
@UKMonkey: Compilers have no choice in this matter. The Standard strictly defines overload resolution for constructors, and the number of arguments alone rules out the default constructor. Efficiency is not a factor in that.
– MSalters
Jan 7 at 13:40
@UKMonkey: Compilers have no choice in this matter. The Standard strictly defines overload resolution for constructors, and the number of arguments alone rules out the default constructor. Efficiency is not a factor in that.
– MSalters
Jan 7 at 13:40
7
7
It only leads to undefined behavior if the variable is read from or written to. If its address or identity is stored but the value of the variable is never read from or written to, no UB occurs. It is, however, very fragile.
– Yakk - Adam Nevraumont
Jan 7 at 15:29
It only leads to undefined behavior if the variable is read from or written to. If its address or identity is stored but the value of the variable is never read from or written to, no UB occurs. It is, however, very fragile.
– Yakk - Adam Nevraumont
Jan 7 at 15:29
3
3
At least in C (can't think of any good reason in c++ though except the original compatibility with c) there's good reason for having definition and declaration separately though:
Foo *foo = malloc(sizeof(*foo));
is a common idiom and avoids common pitfalls when refactoring code.– Voo
Jan 7 at 20:51
At least in C (can't think of any good reason in c++ though except the original compatibility with c) there's good reason for having definition and declaration separately though:
Foo *foo = malloc(sizeof(*foo));
is a common idiom and avoids common pitfalls when refactoring code.– Voo
Jan 7 at 20:51
|
show 3 more comments
Since "root" on the RHS is a flat-out undeclared variable.
It's not undeclared. It is declared by that very same statement. However, root
is uninitialised at the point where AddLeaf(root)
is called, so when the value of the object is used (compared to null etc.) within the function, the behaviour is undefined.
Yes, using a variable in its own declaration is allowed, but using its value is not. Pretty much all you can do with it is take the address or create a reference, or expressions that only deal with type of the sub expression such as sizeof
and alignof
.
Yes, there are use cases although they may be rare. For example, you might want to represent a graph, and you might have a constructor for node that takes a pointer to linked node as an argument and you might want to be able to represent a node that links with itself. Thus you might write Node n(&n)
. I won't argue whether that would be a good design for a graph API.
Recursive lambda is more reasonable use case: coliru.stacked-crooked.com/a/1c4693c645d76d34
– R2RT
Jan 17 at 11:52
add a comment |
Since "root" on the RHS is a flat-out undeclared variable.
It's not undeclared. It is declared by that very same statement. However, root
is uninitialised at the point where AddLeaf(root)
is called, so when the value of the object is used (compared to null etc.) within the function, the behaviour is undefined.
Yes, using a variable in its own declaration is allowed, but using its value is not. Pretty much all you can do with it is take the address or create a reference, or expressions that only deal with type of the sub expression such as sizeof
and alignof
.
Yes, there are use cases although they may be rare. For example, you might want to represent a graph, and you might have a constructor for node that takes a pointer to linked node as an argument and you might want to be able to represent a node that links with itself. Thus you might write Node n(&n)
. I won't argue whether that would be a good design for a graph API.
Recursive lambda is more reasonable use case: coliru.stacked-crooked.com/a/1c4693c645d76d34
– R2RT
Jan 17 at 11:52
add a comment |
Since "root" on the RHS is a flat-out undeclared variable.
It's not undeclared. It is declared by that very same statement. However, root
is uninitialised at the point where AddLeaf(root)
is called, so when the value of the object is used (compared to null etc.) within the function, the behaviour is undefined.
Yes, using a variable in its own declaration is allowed, but using its value is not. Pretty much all you can do with it is take the address or create a reference, or expressions that only deal with type of the sub expression such as sizeof
and alignof
.
Yes, there are use cases although they may be rare. For example, you might want to represent a graph, and you might have a constructor for node that takes a pointer to linked node as an argument and you might want to be able to represent a node that links with itself. Thus you might write Node n(&n)
. I won't argue whether that would be a good design for a graph API.
Since "root" on the RHS is a flat-out undeclared variable.
It's not undeclared. It is declared by that very same statement. However, root
is uninitialised at the point where AddLeaf(root)
is called, so when the value of the object is used (compared to null etc.) within the function, the behaviour is undefined.
Yes, using a variable in its own declaration is allowed, but using its value is not. Pretty much all you can do with it is take the address or create a reference, or expressions that only deal with type of the sub expression such as sizeof
and alignof
.
Yes, there are use cases although they may be rare. For example, you might want to represent a graph, and you might have a constructor for node that takes a pointer to linked node as an argument and you might want to be able to represent a node that links with itself. Thus you might write Node n(&n)
. I won't argue whether that would be a good design for a graph API.
edited Jan 17 at 11:59
answered Jan 7 at 11:06
eerorikaeerorika
90.1k664136
90.1k664136
Recursive lambda is more reasonable use case: coliru.stacked-crooked.com/a/1c4693c645d76d34
– R2RT
Jan 17 at 11:52
add a comment |
Recursive lambda is more reasonable use case: coliru.stacked-crooked.com/a/1c4693c645d76d34
– R2RT
Jan 17 at 11:52
Recursive lambda is more reasonable use case: coliru.stacked-crooked.com/a/1c4693c645d76d34
– R2RT
Jan 17 at 11:52
Recursive lambda is more reasonable use case: coliru.stacked-crooked.com/a/1c4693c645d76d34
– R2RT
Jan 17 at 11:52
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54073039%2fc-compiler-allows-circular-definition%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
7
int x = x + 1;
what bug? What did you expect to happen?– Kamil Cuk
Jan 7 at 11:00
First the variable is initialized (int std::shared_ptr's case with null pointer), then the assignment is performed. Try using a constructor and it will not be allowed.
– rkapl
Jan 7 at 11:02
2
@rkapl no, the variable is not initialized in this case until RHS completes. This statement is known as copy initialization.
– Ruslan
Jan 7 at 21:39
@Ruslan -- you are right, thanks.
– rkapl
Jan 8 at 21:06