Implicit conversion from int to shared_ptr - c++

Consider the code below:
#include <iostream>
#include <memory>
void f(std::shared_ptr<int> sp) {}
template <typename FuncType, typename PtrType>
auto call_f(FuncType f, PtrType p) -> decltype(f(p))
{
return f(p);
}
int main()
{
f(0); // doesn't work for any other int != 0, thanks #Rupesh
// call_f(f, 0); // error, cannot convert int to shared_ptr
}
In the first line in main(), the integer 0 is converted to a std::shared_ptr<int> and the call f(0) succeeds without any problem. However, using a template to call the function make things different. Second line will not compile anymore, the error being
error: could not convert 'p' from 'int' to 'std::shared_ptr<int>'
My questions are:
Why does the first call succeed and the second doesn't? Is there anything I'm missing here?
I don't understand also how the conversion from int to std::shared_ptr is being performed in the call f(0), as it looks like std::shared_ptr has only explicit constructors.
PS: A variant of this example appears in Scott Meyers' Effective Modern C++ Item 8, as a way of protecting such calls with nullptr.

std::shared_ptr has a constructor that takes std::nullptr_t, literal 0 is a null pointer constant that is convertiable to std::nullptr_t from the draft C++ standard section 4.10 [conv.ptr] (emphasis mine going forward):
A null pointer constant is an integral constant expression (5.19) prvalue of integer type that evaluates to zero or a prvalue of type
std::nullptr_t. A null pointer constant can be converted to a
pointer type; the result is the null pointer value of that type and is
distinguishable from every other value of object pointer or function
pointer type. Such a conversion is called a null pointer conversion.
Two null pointer values of the same type shall compare equal. The
conversion of a null pointer constant to a pointer to cv-qualified
type is a single conversion, and not the sequence of a pointer
conversion followed by a qualification conversion (4.4). A null
pointer constant of integral type can be converted to a prvalue of
type std::nullptr_t. [ Note: The resulting prvalue is not a null
pointer value. —end note ]
in your second case p is being deduced as type int which although has the value zero is no longer a null pointer constant and so does not fit the same case.
As T.C. points out the wording was changed with DR 903 which requires an integer literal with value zero as opposed to an integral constant expression which evaluates to zero:
A null pointer constant is an integer literal (2.14.2) with value
zero or a prvalue of type std::nullptr_t. A null pointer constant
can be converted to a pointer type; the result is the null pointer
value of that type and is distinguishable from every other value of
object pointer or function pointer type.

Per [conv.ptr]/1 (quoting N4296 here):
A null pointer constant is an integer literal (2.13.2) with value zero or a prvalue of type std::nullptr_t. ... A null pointer constant of integral type can be converted to a prvalue of type std::nullptr_t.
shared_ptr has a non-explicit constructor that accepts std::nullptr_t per [util.smartptr.shared.const]/1:
constexpr shared_ptr(nullptr_t) noexcept : shared_ptr() { }
which constructs an empty, non-owning shared_ptr.
When you call f(0) directly, 0 is a null pointer constant that is implicitly converted to shared_ptr<int> by the above constructor. When you instead call call_f(f, 0), the type of the literal 0 is deduced to int and of course an int cannot be converted to a shared_ptr<int>.

The firs call f(0) is compiled as f(nullptr), which is fine for the compiler (but it should not be in my opinion). The second call will create declaration for a function to work on any int, which is illegal.
Funny thing is, that even this code works:
f(3-3);
f(3*0);

Related

What is pointer() in unique_ptr?

I read now unique_ptr source code in libstdc++.
public:
typedef _Tp* pointer;
typedef _Tp element_type;
typedef _Tp_Deleter deleter_type;
// Constructors.
unique_ptr()
: _M_t(pointer(), deleter_type())
{ static_assert(!std::is_pointer<deleter_type>::value,
"constructed with null function pointer deleter"); }
I don´t understand.Does "pointer()" call constructor? but "pointer" is an alias for type _Tp*
For all types, T() is an expression that value-initialises an unnamed instance of that type. For non-class, non-array types, value-initialisation is zero-initialisation
the object's initial value is the integral constant zero explicitly converted to T
And for a pointer type, that's a null pointer
This expression
pointer()
zero-initializes the data member pointer of the class that has a pointer type. For pointer types it means setting a pointer to a null pointer.
From the C++ 14 Standard (8.5 Initializers)
11 An object whose initializer is an empty set of parentheses, i.e.,
(), shall be value-initialized
and
8 To value-initialize an object of type T means:
(8.4) — otherwise, the object is zero-initialized.
Further
6 To zero-initialize an object or reference of type T means:
(6.1) — if T is a scalar type (3.9), the object is initialized to the
value obtained by converting the integer literal 0 (zero) to T;104
and in the footnote 104 there is written
As specified in 4.10, converting an integer literal whose value
is 0 to a pointer type results in a null pointer value

Can nullptr be converted to uintptr_t? Different compilers disagree

Consider this program:
#include <cstdint>
using my_time_t = uintptr_t;
int main() {
const my_time_t t = my_time_t(nullptr);
}
It failed to compile with msvc v19.24:
<source>(5): error C2440: '<function-style-cast>': cannot convert from 'nullptr' to 'my_time_t'
<source>(5): note: A native nullptr can only be converted to bool or, using reinterpret_cast, to an integral type
<source>(5): error C2789: 't': an object of const-qualified type must be initialized
<source>(5): note: see declaration of 't'
Compiler returned: 2
but clang (9.0.1) and gcc (9.2.1) "eat" this code without any errors.
I like the MSVC behaviour, but is it confirmed by standard?
In other words is it bug in clang/gcc or it is possible to interpret
standard that this is right behaviour from gcc/clang?
In my opinion MSVC is not behaving standard-conform.
I am basing this answer on C++17 (draft N4659), but C++14 and C++11 have equivalent wording.
my_time_t(nullptr) is a postfix-expression and because my_time_t is a type and (nullptr) is a single expression in a parenthesized initializer list, it is exactly equivalent to an explicit cast expression. ([expr.type.conv]/2)
The explicit cast tries a few different specific C++ casts (with extensions), in particular also reinterpret_cast. ([expr.cast]/4.4) The casts tried before reinterpret_cast are const_cast and static_cast (with extensions and also in combination), but none of these can cast std::nullptr_t to an integral type.
But reinterpret_cast<my_time_t>(nullptr) should succeed because [expr.reinterpret.cast]/4 says that a value of type std::nullptr_t can be converted to an integral type as if by reinterpret_cast<my_time_t>((void*)0), which is possible because my_time_t = std::uintptr_t should be a type large enough to represent all pointer values and under this condition the same standard paragraph allows the conversion of void* to an integral type.
It is particularly strange that MSVC does allow the conversion if cast notation rather than functional notation is used:
const my_time_t t = (my_time_t)nullptr;
Although I can find no explicit mention in this Working Draft C++ Standard (from 2014) that conversion from std::nullptr_t to an integral type is forbidden, there is also no mention that such a conversion is allowed!
However, the case of conversion from std::nullptr_t to bool is explicitly mentioned:
4.12 Boolean conversions A prvalue of arithmetic, unscoped enumeration, pointer, or pointer to member type can be converted to a
prvalue of type bool. A zero value, null pointer value, or null member
pointer value is converted to false; any other value is converted to
true. For direct-initialization (8.5), a prvalue of type
std::nullptr_t can be converted to a prvalue of type bool; the
resulting value is false.
Further, the only place in this draft document where conversion from std::nullptr_t to an integral type is mentioned, is in the "reinterpret_cast" section:
5.2.10 Reinterpret cast
...
(4) A pointer can be explicitly converted to any integral type large enough to hold it. The mapping function is
implementation-defined. [ Note: It is intended to be unsurprising to
those who know the addressing structure of the underlying machine. —
end note ] A value of type std::nullptr_t can be converted to an
integral type; the conversion has the same meaning and validity as a
conversion of (void*)0 to the integral type. [Note: A reinterpret_cast
cannot be used to convert a value of any type to the type
std::nullptr_t. — end note ]
So, from these two observations, one could (IMHO) reasonably surmise that the MSVC compiler is correct.
EDIT: However, your use of the "functional notation cast" may actually suggest the opposite! The MSVC compiler has no problem using a C-style cast in, for example:
uintptr_t answer = (uintptr_t)(nullptr);
but (as in your code), it complains about this:
uintptr_t answer = uintptr_t(nullptr); // error C2440: '<function-style-cast>': cannot convert from 'nullptr' to 'uintptr_t'
Yet, from the same Draft Standard:
5.2.3 Explicit type conversion (functional notation)
(1) A simple-type-specifier (7.1.6.2) or typename-specifier (14.6) followed
by a parenthesized expression-list constructs a value of the specified
type given the expression list. If the expression list is a single
expression, the type conversion expression is equivalent (in
definedness, and if defined in meaning) to the corresponding cast
expression (5.4). ...
The "corresponding cast expression (5.4)" can refer to a C-style cast.
All are standard conformant (ref. draft n4659 for C++).
nullptr is defined in [lex.nullptr] as:
The pointer literal is the keyword nullptr. It is a prvalue of type std::nullptr_t. [ Note: ..., a prvalue of this type is
a null pointer constant and can be converted to a null pointer value or null member pointer value.]
Even if notes are non normative, this one makes clear that for the standard, nullptr is expected to be converted to a null pointer value.
We later find in [conv.ptr]:
A null pointer constant is an integer literal with value zero or a prvalue of type std::nullptr_t. A
null pointer constant can be converted to a pointer type; .... A null pointer constant of integral type can
be converted to a prvalue of type std::nullptr_t.
Here again what is required by the standard is that 0 can be converted to a std::nullptr_t and that nullptr can be converted to any pointer type.
My reading is that the standard has no requirement on whether nullptr can be directly converted to an integral type or not. From that point on:
MSVC has a strict reading and forbid the conversion
Clang and gcc behaves as if an intermediary void * conversion was involved.

Is converting a bool (false) to a pointer legal in C++?

I've recently stumbled unto something strange: converting a boolean to pointer works in Visual Studio 2013 and 2015 but not on GCC nor Clang (tried in 3.5).
#include <iostream>
using namespace std;
void foo(int *ptr)
{
std::cout << "foo";
}
int main()
{
foo(false);
}
Error in GCC:
main.cpp: In function 'int main()':
main.cpp:13:13: error: cannot convert 'bool' to 'int*' for argument '1' to 'void foo(int*)'
foo(false);
^
My guess is that false is converted to 0 which is equivalent to NULL. Replacing the call to foo with foo(true) causes the compilation to fail with every compiler.
So my question is: is this code supposed to compile? I fail to see the benefit of converting false to a pointer, it seems to me that it would only be the cause of bugs after misuse / refactoring etc
This should not be accepted since C++11.
See Pointer conversions (emphasis mine):
A null pointer constant (see NULL), can be converted to any pointer type, and the result is the null pointer value of that type. Such conversion (known as null pointer conversion) is allowed to convert to a cv-qualified type as a single conversion, that is, it's not considered a combination of numeric and qualifying conversions.
Note since C++11 a null pointer constant might be an integer literal with value zero (or a prvalue of type std::nullptr_t), while false is not, it's a boolean literal.
And until C++11 null pointer constant is defined as an integral constant expression rvalue of integer type that evaluates to zero, while false is fine. (GCC will give a warning for it.)
From the standard, $4.10/1 Pointer conversions [conv.ptr] (emphasis mine)
A null pointer constant is an integer literal (2.13.2) with value zero or a prvalue of type std::nullptr_t.
The conversion of a null pointer constant to a pointer to cv-qualified
type is a single conversion, and not the sequence of a pointer
conversion followed by a qualification conversion (4.4).

The address of a function matching a bool vs const void* overload

I'm reading Unexpected value using random number generator as a function in C++ and the comments and current answer say that the user is outputting the address of the function. That sounded reasonable. I assumed that a function-to-pointer conversion was occurring and therefore matching the const void* overload, however upon testing it myself, I get different results in GCC/Clang vs MSVC. The following test program:
#include <iostream>
void test()
{
}
void func(bool)
{
std::cout << "bool";
}
void func(const void*)
{
std::cout << "const void*";
}
int main()
{
func(test);
}
outputs bool in GCC/Clang (coliru)
and const void* in MSVC (rextester warning live collaboration link)
N3337 says:
[conv.func]
An lvalue of function type T can be converted to a prvalue of type
"pointer to T." The result is a pointer to the function.
[conv.bool]
A prvalue of arithmetic, unscoped enumeration, pointer, or pointer to
member type can be converted to a prvalue of type bool. A zero
value, null pointer value, or null member pointer value is converted
to false; any other value is converted to true. A prvalue of type
std::nullptr_t can be converted to a prvalue of type bool; the
resulting value is false.
So a pointer which is not a null pointer value converted to bool should equal true, explaining the warning given by GCC/Clang.
Then Table 12 Conversions under [over.ics.scs] gives a function-to-pointer conversion an "Exact Match" rank and boolean conversions "Conversion" rank. [over.ics.rank]/4 then says:
Standard conversion sequences are ordered by their ranks: an Exact
Match is a better conversion than a Promotion, which is a better
conversion than a Conversion. Two conversion sequences with the same
rank are indistinguishable unless one of the following rules applies:
— A conversion that does not convert a pointer, a pointer to member,
or std::nullptr_t to bool is better than one that does.
— [...]
I am not a language lawyer so I hope that I quoted the right sections.
However MSVC will call the const void* overload even if the bool overload is absent, and vice versa: GCC/Clang will call the bool overload even if the const void* overload is absent. So I'm not clear on the conversions here. Can somebody clear this up for me?
Seems like a bug (or extension) in MSVC. The standard does not define any standard conversions from a "pointer to function" to a "pointer to void."
It's hard to provide a quote for the absence of something, but the closest I can do is C++11 4.10/2 [conv.ptr]:
A prvalue of type “pointer to cv T,” where T is an object type, can be converted to a prvalue of type “pointer
to cv void”. The result of converting a “pointer to cv T” to a “pointer to cv void” points to the start of
the storage location where the object of type T resides, as if the object is a most derived object (1.8) of type
T (that is, not a base class subobject). The null pointer value is converted to the null pointer value of the
destination type.
Together with 3.9/8 [basic.types]:
An object type is a (possibly cv-qualified) type that is not a function type, not a reference type, and not a
void type.
(emphasis mine)
Using /Za to disable extensions will disable the non-standard conversion.

Pointed template type deduced from a nullptr?

Considering the function :
template <class T> void f(const T* const ptr);
What is T for f(nullptr) ?
I would have to answer this with there is none. From § 2.14.7/1 (emphasis mine):
The pointer literal is the keyword nullptr. It is a prvalue of type std::nullptr_t. [Note: std::nullptr_t is a distinct type that is neither a pointer type nor a pointer to member type; rather, a prvalue of this type is a null pointer constant and can be converted to a null pointer value or null member pointer value.
T * would have to be std::nullptr_t and since std::nullptr_t is not a pointer type, that isn't possible. Trying to call it with nullptr on GCC 4.7.2 gives an error indicating that it was trying to call f(std::nullptr_t), but only had f(const T *), which agrees with the fact that a std::nullptr_t is not a T *.