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;
}







22















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









share|improve this question


















  • 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


















22















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









share|improve this question


















  • 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














22












22








22


2






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









share|improve this question














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






share|improve this question













share|improve this question











share|improve this question




share|improve this question










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














  • 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












2 Answers
2






active

oldest

votes


















23














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.






share|improve this answer


























  • 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 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





    @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





















13















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.






share|improve this answer


























  • Recursive lambda is more reasonable use case: coliru.stacked-crooked.com/a/1c4693c645d76d34

    – R2RT
    Jan 17 at 11:52












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
});


}
});














draft saved

draft discarded


















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









23














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.






share|improve this answer


























  • 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 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





    @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


















23














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.






share|improve this answer


























  • 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 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





    @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
















23












23








23







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.






share|improve this answer















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.







share|improve this answer














share|improve this answer



share|improve this answer








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 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





    @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






  • 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








  • 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















13















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.






share|improve this answer


























  • Recursive lambda is more reasonable use case: coliru.stacked-crooked.com/a/1c4693c645d76d34

    – R2RT
    Jan 17 at 11:52
















13















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.






share|improve this answer


























  • Recursive lambda is more reasonable use case: coliru.stacked-crooked.com/a/1c4693c645d76d34

    – R2RT
    Jan 17 at 11:52














13












13








13








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.






share|improve this answer
















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.







share|improve this answer














share|improve this answer



share|improve this answer








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



















  • 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


















draft saved

draft discarded




















































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.




draft saved


draft discarded














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





















































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







Popular posts from this blog

Wiesbaden

Marschland

Dieringhausen