1

I wonder, what could be a possible inherent difference between A a; and A a{}; (class A, no constructor)?

I was just playing with code, and found out something very interesting, where declaring a global class instance as A a; was producing a different result compared to using A a{}; instead.

This is intriguing because I've understood that:

  • Declaring a class instance without initialization calls the default constructor (constructor with no parameters, if it exists).

  • a{} is an expression that creates a class instance and calls the default constructor.

So, basically the same thing, but they did produce different results in a certain condition, although it's found in the realm of possible UB by breaking ODR.

My question is, how can a program produce a different result (even though it is a wrong code), if A a; is strictly equivalent to A a{};?

Logically, there should be some difference between their internal implementation to produce a different result, while everything else stays exactly the same. I wonder what that difference is.

I'm using Visual studio 2017.

//extern_src.cpp

class A {
public:
    int a=24;
};

A extern_a; // << here, if i put {} at the end, different results comes out;

//src.cpp

#include <iostream>
class A {
public:
    int a=3;
};

int main() {
    extern A extern_a;
    A a;
    std::cout << a.a << " " << extern_a.a << std::endl;
    return 0;
}

Without {}, the code prints out 3 3. So for both a and extern_a, the constructor of class A, which is defined in src.cpp, is called.

With {}, the code prints out 3 24. In this case, the constructor that's defined in extern_src.cpp is called for extern_A.

It seems like the default constructor is not immediately called, but only called later in the compile process in the first case (class instance declaration without {}).

I do understand this is breaking ODR by having multiple definitions for a single class. But my question is, if A a; is completely equivalent to A a{};, as they are both calling the default constructor, then how can they ever produce a different result?

L. F.
  • 16,219
  • 7
  • 33
  • 67
Pleasure
  • 37
  • 6
  • 2
    If you understand that you are violating the contract, then you already know that no answer to your question is meaningful. On the other hand, if you're suggesting that we should ignore the ODR violation and that it is not a crucial part of your observations, why do you continue to include it in your [mcve]? – Lightness Races in Orbit Jul 14 '19 at 13:22
  • To make previous reply more clear - your code exhibits undefined behavior. You defined class A twice. Anything might happen. Remove one definition and it will work. – Radosław Cybulski Jul 14 '19 at 13:25
  • @LightnessRacesinOrbit I've been understanding A a{}; and A a; as exactly same expression. I expected them to be translated into completely equal lower level language and yet it seems like they don't. My question is at what level and how these two expressions have difference, because the example suggests that there must exist a difference. – Pleasure Jul 14 '19 at 13:27
  • 1
    @Pleasure Then your question is based on two false premises: (1) that you can expect any meaningful or useful or predictable behaviours after committing a contract violation and creating UB, which you've already indicated you know is what you're doing, and (2) that `A a{}` and `A a` are exactly the same expression (or, in fact, expressions at all). – Lightness Races in Orbit Jul 14 '19 at 13:32
  • 1
    The difference is that the two definition of `A` differ. They initialise the member `a` differently. Every compilation unit that sees a definition of class `A` must see the SAME definition. Having a member initialised differently in different compilation units, as part of the class definition, breaches ODR and means your code has undefined behaviour. Your expectation that the two will translate into the same lower level language is not guaranteed to be correct. – Peter Jul 14 '19 at 13:32
  • 1
    If you just want to know what the difference is between the two declarations, then _ask that_. Don't try to use UB to frame the question, because all that does is invalidate it – Lightness Races in Orbit Jul 14 '19 at 13:33
  • @Lightness Races in Orbit ok ok it's not expression but declaration. About (1), undefined behaviors are unpredictable. It's outside the rule of the langauge, that's true. However the result should be 'deterministic' as everything in macroscopic world is and therefore does have some meaning and value(even though we're not gonna use that in everyday programming) and yes (2) is exactly what i'm looking for to be answered. I've always thought they are same, but it's not. Why? What do you think is the difference between two declaration statement? – Pleasure Jul 14 '19 at 13:40
  • @Lightness Races in Orbit yes that's true. What i wanted to suggest by the example even bringing in the rule violation was that there must exist some intrinsic difference. Because otherwise, people could've just answered "they're completely same." and that's not completely true. – Pleasure Jul 14 '19 at 13:44
  • _"However the result should be 'deterministic'"_ There's your false assumption! – Lightness Races in Orbit Jul 14 '19 at 19:15
  • You have two different definitions of `A` in two completely different scopes. You are instantiating variables in those scopes. Hence they initialize their variables in different ways. Why is that so hard to understand? – Remy Lebeau Jul 14 '19 at 21:01
  • Your code is ill-formed , as you will see if you try compiling what you posted. Please copy and paste the exact code that you ran to get the unexpected output , instead of making something up that looks roughly similar – M.M Jul 14 '19 at 23:03
  • @RemyLebeau please read the code again. In both cases initialized class instance is declared in different file by the mean of extern keyword. However for the case 1 where variable is initially declared as A a; constructor outside file scope(one defined in main) was used. But for the case 2 where variable is declared as A a{}; constructor inside file scope was used. – Pleasure Jul 16 '19 at 05:23
  • @M.M it's compiling in my VS2017. – Pleasure Jul 16 '19 at 05:24
  • @Pleasure the code's been edited (by someone else) since I made my comment, do both versions compile for you? – M.M Jul 16 '19 at 05:26
  • @LightnessRacesinOrbit Why? Unpredictability doesn't mean randomness. Undefined means it's outside the rule and standard doesn't provide guarantee or requirement about what's gonna happen, it doesn't mean the results comes out of blue. Get me one mechanism of undefined behavior that produces different results when initial conditions are strictly fixed. – Pleasure Jul 16 '19 at 05:33
  • @M.M Oh sorry i'm very new to actually participating in the community I will check that out. // Yes you're right – Pleasure Jul 16 '19 at 05:34
  • @Pleasure If _all_ initial conditions were strictly fixed then you could probably expect the same results, but that's practically almost never the case. Garbage in memory at runtime is not generally controllable by some compiler, for example. The behaviour of the optimiser can change between versions. The content of a program may differ simply by when it was built (think about the use of date/time macros, for example) which in turn may effect symptoms of UB. Overwhelmingly, the symptoms of UB are non-deterministic in practice within the context of a single executable on a modern system. – Lightness Races in Orbit Jul 16 '19 at 10:30

1 Answers1

1

You have already shown that your program violates the One Definition Rule. Per [basic.def.odr]/12:

There can be more than one definition of a class type [...] Given such an entity named D defined in more than one translation unit, then

  • each definition of D shall consist of the same sequence of tokens; and

  • [...]

[...] If the definitions of D do not satisfy these requirements, then the behavior is undefined.

Your definitions of A do not consist of the same sequence of tokens, so the behavior is undefined. When the behavior is undefined all bets are off. Even the same program can behave differently when compiled again. Not to say that A a; and A a{}; are different statements (or more precisely, simple-declarations, but not expressions). Per [defns.undefined]:

undefined behavior

behavior for which this document imposes no requirements

[ Note: Undefined behavior may be expected when this document omits any explicit definition of behavior or when a program uses an erroneous construct or erroneous data. Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message). Many erroneous program constructs do not engender undefined behavior; they are required to be diagnosed. Evaluation of a constant expression never exhibits behavior explicitly specified as undefined in [intro] through [cpp] of this document ([expr.const]). — end note ]

See also Undefined, unspecified and implementation-defined behavior to get an idea of the possible consequences of undefined behavior. In particular, one version of GCC (1.17) tried to start the games NetHack, Rogue, and Towers of Hanoi when encountering certain kinds of undefined behavior. [1]

L. F.
  • 16,219
  • 7
  • 33
  • 67
  • "Not to say that A a; and A a{}; are different statements." So what's the difference? That's the big question for me here. – Pleasure Jul 16 '19 at 05:35
  • @Pleasure `A a;` is default initialization, and `A a{};` is value initialization. See https://stackoverflow.com/q/1613341. – L. F. Jul 16 '19 at 07:02