2
int main(){
 int a = 5; // copy initialization.

 int b(a); // direct initialization.
 int *c = new int(a); // also direct initialization, on heap.

 int d = int(a); // functional cast, also direct initialization? 
                 // or copy initialization?

 return 0;
}

I have 2 questions on this:

1 - It's not clear to me if int(a); is only a cast, or if it's also an initializer. Is d being copy initialized or is it being direct initialized? i.e is the expression equal to this:

int d = 5;

or this

int d(5);

2- I want to confirm if new int(a); is only an initializer and not a cast to int. Also want to confirm if it's direct initialization and not copy initialization.

I have read section (3) of the reference but the answer is not clear to me: https://en.cppreference.com/w/cpp/language/direct_initialization

  1. initialization of a prvalue temporary (until C++17)the result object of a prvalue (since C++17) by functional cast or with a parenthesized expression list
Dan
  • 2,694
  • 1
  • 6
  • 19
  • 1
    Not sure... is this a `language-lawyer` tag question? – Eljay Jul 15 '21 at 20:24
  • Side note: [Good presentation on initialization and how deep the rabbit hole goes](https://www.youtube.com/watch?v=7DTlWPgX6zs) – user4581301 Jul 15 '21 at 20:28
  • `int d = int(a);` is the same as `int d = (int)a;` – Eljay Jul 15 '21 at 20:56
  • Relevant section of the standard: https://eel.is/c++draft/expr.type.conv –  Jul 15 '21 at 21:13
  • I also found this https://stackoverflow.com/a/1051468. So like @Eljay said, `int(a)` creates a temp variable and `d` gets copy initialized with it? In case a class is used ie `test d = test(a);` the constructor of `test` gets called? how about the `new` case, it's always a direct initialization and not a cast, or is it a copy initialization? – Dan Jul 15 '21 at 21:16
  • I would have thought this was the same as `std::string s = std::string("stuff");` rather than `std::string s = (std::string)"stuff";` otherwise the syntax seems inconsistent. – Galik Jul 15 '21 at 21:18
  • @Galik how so? The C-style cast should simply do the same thing as passing a string literal to a function that accepts a `std::string` as argument. –  Jul 15 '21 at 21:22
  • @Galik: You are right about the "this is the same as" part, but the "rather than" part presupposes that there is a difference between the two. If both are the same, it can be the same as both. – Ben Voigt Jul 15 '21 at 21:23
  • @BenVoigt But doesn't `(std::string)` call `operator std::string()` on whatever object it is applied to, whereas `std::string("stuff");` calls a constructor of `std::string`? – Galik Jul 15 '21 at 21:39
  • @Galik: Those are both valid ways of defining and evoking conversions. There is no pairing between the two ways to define conversions and the two ways to trigger conversion -- either syntax will trigger either conversion. – Ben Voigt Jul 15 '21 at 21:43

1 Answers1

3

Your analysis is missing some steps.

int *c = new int(a);
  1. This performs direct initialization of a dynamically-allocated int instance.

  2. The new operator evaluates to a pointer prvalue (type = int*) which is the address of this new int object.

  3. c is copy-initialized from the pointer prvalue.

int d = int(a);
  1. This performs direct initialization of a temporary int instance, which is a prvalue (type = int).

  2. d is copy-initialized from this prvalue.

In both cases, the copy constructor call is eligible for elision in all C++ versions, and elision is guaranteed in the latest Standard.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • Thank you, can I confirm what happens if classes are used, i.e `test t = test(a);`. is `test(a);` a cast or a constructor call . This answer says it's always a cast first: https://stackoverflow.com/a/45505951 – Dan Jul 15 '21 at 21:43
  • @Dan: It's a cast. A cast is syntax to cause a conversion. A conversion may result in a built-in conversion, calling a converting constructor, or calling a user-defined conversion operator. – Ben Voigt Jul 15 '21 at 21:45
  • Thanks again for confirming, by `user-defined conversion operator` you are referring to operator overloading right, something like this: `struct test { int operator()(int in) { /**/ } };` ? – Dan Jul 15 '21 at 21:50
  • @Dan: What I'm talking about is `struct test { operator int() { /* the input is "this" */ } };`. It uses the `operator` keyword as does operator overloading, but I wouldn't really call it "operator overloading" because the rules for choosing a conversion sequence are quite different than the rules for overload selection. Your example was an overload of the `()` function-call operator, which is not used for conversions at all. – Ben Voigt Jul 15 '21 at 22:00
  • 1
    _The `new` operator evaluates to a pointer prvalue (type = `int* &&`)_ Where did `&&` come from?! – Language Lawyer Jul 15 '21 at 22:32
  • @LanguageLawyer: Because it's a prvalue, it will activate move constructors, etc. – Ben Voigt Jul 15 '21 at 22:33
  • `int` does not have any constructors. Even if there were, which constructor gets activated doesn't affect the type of an expression. – Language Lawyer Jul 15 '21 at 22:34
  • @LanguageLawyer: So? What does moving an `int*` have to do with `int` constructors? – Ben Voigt Jul 15 '21 at 22:36
  • @BenVoigt a bit confused by `/* the input is "this" */`. In this example `struct test { operator int(){ return 5; } };` and then `int a = test();`, `a` will be `5`, there is no input. `this` points to the class instance. – Dan Jul 15 '21 at 22:48
  • 1
    @Dan: "`this` points to the class instance". Exactly. The input to the conversion operation is the instance of `test` and the output is `int`. I'm contrasting with your snippet `struct test { int operator()(int in) { /**/ } };` which I meant was supposed to transform `in` into the result `int`, but is not a conversion. – Ben Voigt Jul 15 '21 at 22:50
  • @BenVoigt Can I also confirm if `test t(a);` is not a function style cast and it's simply a direct initialization? – Dan Jul 16 '21 at 03:55
  • @Dan: That's correct. `test t(a);` is not a cast, it is direct-initialization. It may or may not cause a conversion, but it will always call a `test::test` constructor. – Ben Voigt Jul 16 '21 at 15:06
  • There's no way for an expression that initially (before [\[expr.type\]/1](https://timsong-cpp.github.io/cppwp/n4861/expr.type#1)) has a type like `int* &&` or `int&&` to be a prvalue. – bogdan Jul 16 '21 at 16:30
  • I could nitpick some more, but I'm not sure if I should... hey, it's [tag:language-lawyer], so here we go: there are no copy constructor calls involved in any of those cases, and we shouldn't talk about copy elision, since that essentially only makes sense in the context of classes ([\[class.copy.elision\]/1](https://timsong-cpp.github.io/cppwp/n4861/class.copy.elision#1)). – bogdan Jul 16 '21 at 17:20
  • Also, in `int d = int(a)`, `int(a)` would (at least conceptually) be creating a temporary `int` before C++17, but afterwards there's no temporary at all: `int(a)` is a prvalue expression that describes the initialization of an `int`, and its [result object](https://timsong-cpp.github.io/cppwp/n4861/basic.lval#5) is `d`. Interestingly, even though it has the syntactic form of a copy-initialization, it actually [performs direct-initialization](https://timsong-cpp.github.io/cppwp/n4861/expr.static.cast#4). Not that any of this would really matter for `int`s :-). – bogdan Jul 16 '21 at 17:22
  • @bogdan: That's the last sentence in my answer concerning elision of the copy constructor call. Yes, for `int` the copy constructor call is trivial, but the Standard explicitly allows talking about special member functions of primitive types even though they are all trivial. – Ben Voigt Jul 16 '21 at 17:59
  • @BenVoigt "the Standard explicitly allows talking about special member functions of primitive types" - It does? Where? – bogdan Jul 16 '21 at 19:29
  • @bogdan: For example https://eel.is/c++draft/expr.prim.id.dtor#2 – Ben Voigt Jul 16 '21 at 21:08
  • All the type traits, most of which query the behavior of special member functions, also work just fine on primitive types. – Ben Voigt Jul 16 '21 at 21:16
  • 1
    @BenVoigt Look just one paragraph above :-) : "[...] names the destructor of T **if T is a class type**, otherwise the id-expression is said to name a **pseudo**-destructor". It specifically avoids talking about destructors for non-class types. – bogdan Jul 16 '21 at 23:29
  • 1
    Most of the type traits query the behavior of types; their descriptions hardly ever mention special member functions, after a qualifier like "if T is a class type". The one exception I can find, `is_nothrow_destructible`, is most likely an oversight, because `is_trivially_destructible` carefully separates class and non-class types. The traits also work on references and functions too, and that doesn't mean we can talk about special functions of those kinds of types. I don't see any explicit allowance for talking about special member functions of primitive types in any of these examples. – bogdan Jul 16 '21 at 23:32
  • @bogdan: That paragraph supports my point as well but you cut out the part that does. Read it again in full context. "Denotes a destructor of type T" applies not only to class types, but explicitly to both class types and non-class types. – Ben Voigt Jul 18 '21 at 05:53
  • 1
    "Denotes a destructor of type T" describes the syntactic form of an _id-expression_, see https://eel.is/c++draft/expr.prim.id#unqual-1.sentence-4 – Language Lawyer Jul 18 '21 at 06:01
  • As @LanguageLawyer pointed out, the quote you mentioned describes a syntactic construct, whose form is indeed uniform between class and non-class types, so it deserves a uniform name. You're looking for *explicit* allowance to talk about special member functions of primitive types; I think "**pseudo**-destructor" is quite explicit ("pseudo-": pretended and not real). If that syntactic uniformity were carried through into semantics, what would be the need for the rest of the paragraph, that *explicitly* separates class and non-class types? – bogdan Jul 18 '21 at 12:31
  • If we're allowed to talk about constructors of non-class types, why does [dcl.init] have separate bullets for class types, where it can say things like "constructors are considered", and non-class types, where there's no such thing? Why are trivially copyable and trivial types defined as they are, naming scalar types and trivially copyable and trivial class types separately, because only the definitions for class types use constructors? Why is there wording like [\[class.union\]/6.1](https://timsong-cpp.github.io/cppwp/n4861/class.union#6.1) (just one random example of many)? – bogdan Jul 18 '21 at 12:45
  • I've only mentioned three random examples, but, those are definitely not the only ones throughout the standard text. There are many such places where the wording could be much simpler, shorter and uniform if only it could talk about special member functions of non-class types. But it isn't, 'cause it doesn't. – bogdan Jul 18 '21 at 12:50