Unobvious feature in variable definition syntax

    A completely innocent-looking piece of C ++ code is proposed. There are no templates, no virtual functions, no inheritance, but the creators of this wonderful language hid a rake in the middle of a clean field.

    struct A {
      A (int i) {}
    };

    struct B {
      B (A a) {}
    };

    int main () {
      int i = 1;
      B b(A(i)); // (1)
      return 0;
    }

    * This source code was highlighted with Source Code Highlighter.


    Question: what type of variable b? Not at all one that could be assumed at first glance.


    Analysis


    Of course, the type of the variable b is not B, otherwise this article would not have been :) I will not immediately give the answer, but instead I will tell you how to get to it without digging into the thousand-page standard.

    First, add some debugging printing:
    #include
    struct A {
      A (int i) { std::cout << 'A';}
    };

    struct B {
      B (A a) { std::cout << 'B';}
    };

    int main () {
      int i = 1;
      B b(A(i)); // (1)
      return 0;
    }

    * This source code was highlighted with Source Code Highlighter.


    If you try to run this code, it turns out that nothing is output at all . But if you replace line (1) with
      B b(A(1));

    suddenly everything starts to work.

    Now let's look carefully at the compiler output with warnings turned on as much as possible.
    $ g++ -W -Wall test.cpp
    x.cpp:2: warning: unused parameter ‘i’
    x.cpp:6: warning: unused parameter ‘a’
    x.cpp: In function ‘int main()’:
    x.cpp:10: warning: unused variable ‘i’

    With the first two lines everything is clear, indeed the parameters of the constructors are not used. But the last line looks very strange. How did i turn out to be unused if it is used on the next line?

    In principle, this information is enough to give a little thought to answer the question. But if smart thoughts do not occur, and you want to play a little more, why not just ask the compiler? RTTI comes to the rescue.

    #include
    #include

    struct A {
      A (int i) {}
    };

    struct B {
      B (A a) {}
    };

    int main () {
      int i = 1;
      B b(A(i)); // (1)
      std::cout << typeid(b).name() << std::endl;
      return 0;
    }

    * This source code was highlighted with Source Code Highlighter.


    When compiling GCC 4.3, the output of this program is a string
    F1B1AE

    in which the information about the type of the variable we need is encrypted (of course, another compiler will give a different line, the output format type_info :: name () is not described in the standard and is left to the discretion of the developer). C ++ filt will help us find out what these letters and numbers mean.
    $ c++filt -t F1B1AE
    B ()(A)

    Here is the answer: this is a function that takes an input of type A parameter and returns a value of type B.

    Cause


    It remains to understand why our line was interpreted in such an unexpected way. The thing is that in a variable type declaration, extra brackets around the name are ignored. For example, we can write
      int (v);

    and that will mean exactly the same thing as
      int v;


    Therefore, the long-suffering line (1) can be rewritten without changing the meaning, removing an extra pair of brackets:
      B b(A i);

    Now we can see with naked eyes that b is a function declaration with one argument of type A, which returns a value of type B.

    At the same time, we explained the strange thing about the unused variable i - indeed, it has nothing to do with the formal parameter i.

    Workarounds


    We only need to explain to the compiler what we really want from it - that is, to get a variable of type B, initialized by a variable of type A. The easiest way is to add extra brackets, like this:
      B b((A(i)));

    or so:
      B b((A)(i));

    This is enough to convince the parser that this is not a function declaration.

    Alternatively, you can use the constructor invocation form using assignment, unless the constructor is explicitly declared:
      B b = A(i);

    Despite the presence of the '=' sign, no unnecessary copying occurs here, which can be easily verified by having a private copy constructor in class B.

    Or you can simply enter an additional variable:
      A a(i);
      B b(a);

    True, this will require unnecessary copying of the variable a, but in many cases this is acceptable.

    Choose the method that seems more understandable to you :)

    Inspired by a post on StackOverflow

    PS Thank you sse  for clarification.

    Also popular now: