4

I am using Visual Studio 2013, and this is what I'm trying to figure out:

#include <iostream>
#include <vector>
using namespace std;

class A
{
public:
    int x = 1;
    bool y = true;

    A(int _x, bool _y) : x(_x), y(_y)
    {
        cout << "Constructor #1" << endl;
    }

    A(std::initializer_list<int> init_list)
    {
        cout << "Constructor #2" << endl;
    }
};


int main(int argc, char** argv)
{
    A Aobj1(10, false);
    A Aobj2(20, "false");
    A Aobj3{30, false};
    A Aobj4{40, "false"};

    return 0;
}

The output is:

Constructor #1
Constructor #1
Constructor #2
Constructor #1
  • The first call to constructor #1 is fine

  • Now the second construction of Aobj2(int, string) calling constructor #1 is strange. How is the compiler calling the (int, bool) constructor with a string argument? The "false" bool is not even converted to an int.

  • Aobj3 is also OK. Although the initializer_list is of type int, the compiler calls this because of brace-initialization and converts the bool to an int.

  • This one again baffles me. I would have expected this to be an error because a string cannot be converted to an int (as it was with the bool), and also expected the compiler to be calling the initializer_list constructor because it is a braced initialization. But the compiler chooses the (int, bool) constructor.

What is some background on the logic of the compiler?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
madu
  • 5,232
  • 14
  • 56
  • 96

2 Answers2

8

Now the second construction of Aobj2(int, string) calling constructor #1 is strange. How is the compiler calling the (int, bool) constructor with a string argument?

More precisely, "false" is of type const char[6] and could decay to const char*, i.e. a pointer, and then could implicitly convert to bool.

A prvalue of integral, floating-point, unscoped enumeration, pointer, and pointer-to-member types can be converted to a prvalue of type bool.

The value zero (for integral, floating-point, and unscoped enumeration) and the null pointer and the null pointer-to-member values become false. All other values become true.

For the 4th case, the parameter std::initializer_list<int> can't match the argument 40, "false", because there're no implicit conversion converting pointer to int. Then the constructor taking int, bool is selected, because "false" could be converted to bool implicitly as the above explained.

Community
  • 1
  • 1
songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • a pointer can be implicitly converted to `bool` but not to `int`?? (*More precisely, "false" is of type const char[6] and could decay to `const char*`, i.e. a pointer, and then could implicitly convert to `boo`l.* **vs.** *because there're no implicit conversion converting pointer to `int`*) – CIsForCookies Nov 01 '18 at 06:42
  • @CIsForCookies Yes. A pointer could implicitly convert to `bool` but not to `int`. https://wandbox.org/permlink/QDgcZJf2gzF4w02h – songyuanyao Nov 01 '18 at 06:48
  • 1
    I find it very hard to understand... I see pointers as a numeric value corresponding to an address, which I can implictly change to a numeric value corrsponding to anything else (i.e. `int`). `bool` is just a very limited `int` that can't reach the values a pointer / `int` can, so the convertion should work the other way around, as I see it. Can you please explain? – CIsForCookies Nov 01 '18 at 06:51
  • 1
    @CIsForCookies Maybe you're confusing with *implicit conversion* and *explicit conversion*? Implicit conversion from pointer to `bool` is allowed, but converting pointer to `int` requires explicit conversion, .e.g. [reinterpret_cast](https://en.cppreference.com/w/cpp/language/reinterpret_cast). It means you could do it but you have to do it explicitly. – songyuanyao Nov 01 '18 at 07:02
  • Not confusing, just not understanding... I'm under the impression that implicit conversion comes when the conversion is somewhat obvious, and I say, pointer is much more like `int` then like `bool` so I would expect the conversion to work the other way around – CIsForCookies Nov 01 '18 at 07:16
  • 3
    @CIsForCookies I get what you meant, but it's not easy to explain.. Converting pointer to `bool` seems straightforward to me, behavior is simple and clear; the converted result depends on whether the pointer is null pointer or not, and then it could be used to represent the pointer is a null pointer or not. Converting to an `int` is not obvious as to `bool`; in concept the pointer is just an object pointing to sth, then what's the purpose to convert it to `int`? We can't use the converted result for arithmetic operation. It might be the memory address, but it's not the general use case of int. – songyuanyao Nov 01 '18 at 07:27
7

"false" is a const char[6] which decays to const char* and pointers are implicitly convertible to bool. That's why the compiler can call your constructor taking a int and a bool, since the compiler is allowed to perform one implicit conversion to make a function argument match.

Jesper Juhl
  • 30,449
  • 3
  • 47
  • 70
  • 1
    Got it. Thanks for the info on "compiler is allowed to make one implicit conversion". I was wondering why the bool is not then converted to an int and called constructor #4. – madu Nov 01 '18 at 06:22
  • "false" is a **not** `const char*`. This answer is wrong. – taskinoor Nov 01 '18 at 06:24
  • @taskinoor `"false"` is a `const char[6]` which decays to a `const char*`...? This is a real tricky issue I once stumbled myself about. In my case, the alternative was `A::A(int, std::string)` and I was really suprised when I realized that `A::A(int, bool)` was preferred. It does make sense but hit me unexpectedly. – Scheff's Cat Nov 01 '18 at 06:46
  • 2
    @Scheff Interesting case. Converting `const char*` to `std::string` requires user-defined conversion (via the constructor of `std::string`); converting `const char*` to `bool` is standard conversion. Then the latter is perferred. – songyuanyao Nov 01 '18 at 06:51
  • @taskinoor I fixed that. – Jesper Juhl Nov 03 '18 at 17:33
  • @JesperJuhl now it looks better. Thanks for the edit. – taskinoor Nov 03 '18 at 18:40
  • 1
    @madu Note that you can use [explicit](https://en.cppreference.com/w/cpp/language/explicit) to say that a certain constructor is *not* allowed to be used by the compiler for implicit conversions. That's often useful to prevent unwanted implicit conversions. – Jesper Juhl Nov 04 '18 at 13:34