C++ Compiler allows circular definition?












18















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
















18















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














18












18








18


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

799516




799516








  • 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


















20














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





















12















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.



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

























    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









    20














    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


















    20














    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
















    20












    20








    20







    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

    295k24250411




    295k24250411













    • 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















    12















    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.



    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






























      12















      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.



      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




























        12












        12








        12








        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.



        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.



        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 7 at 13:55

























        answered Jan 7 at 11:06









        eerorikaeerorika

        77.8k556118




        77.8k556118






























            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

            Mario Kart Wii

            The Binding of Isaac: Rebirth/Afterbirth

            What does “Dominus providebit” mean?