why the const in c++ conversion operator is meaningless? - c++

In "C++ Primer", exercise 14.47, there is a question:
Explain the difference between these two conversion
operators:
struct Integral {
operator const int();
operator int() const;
}
I don't know why the the answer I found on GitHub says that the first const is meaningless, because for one conversion operator should not define return type, this const here is unspecified, it will be ignored by the compiler. But I also found some guys say that it means the function will return a const value.
So, I wonder which one is correct, and why?

it will be ignored by compiler.
This is because of expr#6 which states:
If a prvalue initially has the type cv T, where T is a cv-unqualified non-class, non-array type, the type of the expression is adjusted to T prior to any further analysis.
This means that in your particular example, const int will be adjusted to int before further analysis since int is a built in type and not a class type.
which one is right?
Even though the return type of the first conversion function is const int, it will be adjusted to int prior to any further analysis.
While the const on the second conversion function means that the this pointer inside that function is of type const Integral*. This means that it(the conversion function) can be used with const as well as non-const Integral object. This is from class.this
If the member function is declared const, the type of this is const X*, ...

Related

g++ and clang++ - delete pointer acquired by overloaded conversion operator ambiguity

I was trying to post this code as an answer to this question, by making this pointer wrapper (replacing raw pointer). The idea is to delegate const to its pointee, so that the filter function can't modify the values.
#include <iostream>
#include <vector>
template <typename T>
class my_pointer
{
T *ptr_;
public:
my_pointer(T *ptr = nullptr) : ptr_(ptr) {}
operator T* &() { return ptr_; }
operator T const*() const { return ptr_; }
};
std::vector<my_pointer<int>> filter(std::vector<my_pointer<int>> const& vec)
{
//*vec.front() = 5; // this is supposed to be an error by requirement
return {};
}
int main()
{
std::vector<my_pointer<int>> vec = {new int(0)};
filter(vec);
delete vec.front(); // ambiguity with g++ and clang++
}
Visual C++ 12 and 14 compile this without an error, but GCC and Clang on Coliru claim that there's an ambiguity. I was expecting them to choose non-const std::vector::front overload and then my_pointer::operator T* &, but no. Why's that?
[expr.delete]/1:
The operand shall be of pointer to object type or of class type. If of
class type, the operand is contextually implicitly converted (Clause
[conv]) to a pointer to object type.
[conv]/5, emphasis mine:
Certain language constructs require conversion to a value having one
of a specified set of types appropriate to the construct. An
expression e of class type E appearing in such a context is said
to be contextually implicitly converted to a specified type T and
is well-formed if and only if e can be implicitly converted to a type
T that is determined as follows: E is searched for non-explicit
conversion functions whose return type is cv T or reference to cv T
such that T is allowed by the context. There shall be exactly
one such T.
In your code, there are two such Ts (int * and const int *). It is therefore ill-formed, before you even get to overload resolution.
Note that there's a change in this area between C++11 and C++14. C++11 [expr.delete]/1-2 says
The operand shall have a pointer to object type, or a class type
having a single non-explicit conversion function (12.3.2) to a pointer
to object type. [...]
If the operand has a class type, the operand is converted to a pointer type by calling the above-mentioned conversion function, [...]
Which would, if read literally, permit your code and always call operator const int*() const, because int* & is a reference type, not a pointer to object type. In practice, implementations consider conversion functions to "reference to pointer to object" like operator int*&() as well, and then reject the code because it has more than one qualifying non-explicit conversion function.
The delete expression takes a cast expression as argument, which can be const or not.
vec.front() is not const, but it must first be converted to a pointer for delete. So both candidates const int* and int* are possible candidates; the compiler cannot choose which one you want.
The eaiest to do is to use a cast to resolve the choice. For example:
delete (int*)vec.front();
Remark: it works when you use a get() function instead of a conversion, because the rules are different. The choice of the overloaded function is based on the type of the parameters and the object and not on the return type. Here the non const is the best viable function as vec.front()is not const.

Why does adding two string literals not use operator+?

Edit: I have reformatted the post to be clearer.
Why does this work:
struct A {};
struct B {
B(A){}
};
void operator+(const B&, const B&) {}
int main()
{
A a1, a2;
a1 + a2;
}
and this does not?
struct B {
B(const char*){}
};
void operator+(const B&, const B&) {} //error: invalid operands of types 'const char [6]' and 'const char [6]' to binary 'operator+'|
int main()
{
"Hello" + "world";
}
Essentially, in the first example a1 and a2 both convert to B objects through the implicit conversion and use the operator+(const B&, const B&) to add.
Following from this example, I would have expected "Hello" and "world" to convert to B objects, again through the implicit constructor, and use operator+(const B&, const B&) to add to each other. Instead there is an error, which indicates the C-style strings do not attempt a user-defined conversion to B in order to add. Why is this? Is there a fundamental property that prevents this?
In your first example, overload resolution is allowed to find your operator+:
[C++14: 13.3.1.2/2]: If either operand has a type that is a class or an enumeration, a user-defined operator function might be declared that implements this operator or a user-defined conversion can be necessary to convert the operand to a type that is appropriate for a built-in operator. In this case, overload resolution is used to determine which operator function or built-in operator is to be invoked to implement the operator. [..]
[C++14: 13.3.2/1]: From the set of candidate functions constructed for a given context (13.3.1), a set of viable functions is chosen, from which the best function will be selected by comparing argument conversion sequences for the best fit (13.3.3). The selection of viable functions considers relationships between arguments and function parameters other than the ranking of conversion sequences.
[C++14: 13.3.2/2]: First, to be a viable function, a candidate function shall have enough parameters to agree in number with the arguments in the list.
If there are m arguments in the list, all candidate functions having exactly m parameters are viable.
[..]
[C++14: 13.3.2/3]: Second, for F to be a viable function, there shall exist for each argument an implicit conversion sequence (13.3.3.1) that converts that argument to the corresponding parameter of F. [..]
(You may examine the wording for "implicit conversion sequence" yourself to see that the operator+ call is permissible; the rules are too verbose to warrant verbatim reproduction here.)
However, in your second example, overload resolution is constrained to a basic arithmetic addition mechanism (one which is not defined for const char[N] or const char*), effectively prohibiting any operator+ function from being considered:
[C++14: 13.3.1.2/1]: If no operand of an operator in an expression has a type that is a class or an enumeration, the operator is assumed to be a built-in operator and interpreted according to Clause 5.
[C++14: 5.7/1]: [..] For addition, either both operands shall have arithmetic or unscoped enumeration type, or one operand shall be a pointer to a completely-defined object type and the other shall have integral or unscoped enumeration type. [..]
[C++14: 5.7/3]: The result of the binary + operator is the sum of the operands.
1. Explaining your compiler error:
The reason you can't concatenate two string literals using the '+' operator,
is because string literals are simply arrays of characters, and you can't concatenate two arrays.
Arrays will be implicitly converted to the pointer of their first element.
Or as the standard describes it:
[conv.array]
An lvalue or rvalue of type “array of N T” or “array of unknown bound
of T” can be converted to a prvalue of type “pointer to T”. The result
is a pointer to the first element of the array.
What you are really doing in the example above,
is trying to add two const char pointers together, and that is not possible.
2. Why the string literals aren't implicitly converted:
Since arrays and pointers are fundamental types, you can't provide an implicit conversation operator as you have done in your class example.
The main thing to keep in mind, is that std::string knows how to take in char[], but char[] does not know how to become a std::string. In your example, you've used B, as a replacement to char[], but you've also given it the ability to convert itself to A.
3. Alternatives:
You can concatenate string literals by leaving out the plus operator.
"stack" "overflow"; //this will work as you indented
Optionally, you could make "stack" a std::string, and then use the std::string's overloaded '+' operator:
std::string("stack") + "overflow"; //this will work

Preventing implicit conversion of some arguments in a templated member function

Currently I have a member function defined as such:
template<typename T> bool updateParameter(const std::string& name, const T& data);
With an overload for pointers.
template<typename T> bool updateParameter(const std::string& name, T* data);
I would like to be able to use this function as such:
int test = 20;
updateParameter<int>("name", 0);
updateParameter<int>("Referenced parameter", &test);
This way I can have a parameter object that either owns the data that it represents, or points to a user owned member.
Now the problem that I have is with the current setup MSVC implicitly will convert the const 0 of "name" to a pointer, so it ends up calling the overload designed for pointers. I can use the explicit keyword, but then I can't get the implicit conversion from const char[] to std::string for the name parameter.
Is there a way of telling the compiler, MSVC and GCC that a certain field should not be implicitly converted, or at least for it to prefer the const T& version over the T* version?
This is a VC++ bug. The first argument's conversion is identical for both overloads (char const[5] => std::string const&).
For the second argument, there are two distinct standard conversion sequences though: For the T const&-overload, the conversion is an identity conversion - §13.3.3.1.4/1:
When a parameter of reference type binds directly (8.5.3) to an
argument expression, the implicit conversion sequence is the identity
conversion, unless the argument expression has a type that is a
derived class of the parameter type […]
However, converting 0 to a pointer type has Conversion rank. §4.10 goes
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 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.
And §13.3.3.1.1/3 categorizes that accordingly, while also listing our identity conversion and how both relate:
The best workaround is to simply upgrade VC++, as recent versions select the correct overload (e.g. compare with rextester's VC++).
Another option is to take data by reference instead for your second overload. Ambiguities would be prevented by §13.3.3.2/3.2.6. Or simply don't overload updateParameter at all and provide a second function template instead.

Clang vs GCC vs MSVC template conversion operator - which compiler is right?

I have simple code with conversion operator and it seems like all compilers are giving different results, was curious which compiler, if any, is correct?
I tried different combinations as well, but below ones are the most interesting. Code was compiled using C++11 flag, but the same behavior might be observed in C++03 as well.
#include <iostream>
struct call_operator {
template<typename T>
operator T() {
std::cout << __FUNCTION__ << std::endl;
return {};
}
template<typename T>
operator const T&() const {
std::cout << __FUNCTION__ << std::endl;
static T t;
return t;
}
template<typename T>
operator T&() const {
std::cout << __FUNCTION__ << std::endl;
static T t;
return t;
}
};
int main() {
(void)static_cast<int>(call_operator());
(void)static_cast<const int&>(call_operator());
(void)static_cast<int&>(call_operator());
}
clang-3.6:
operator int
operator const int &
operator int &
g++-4.9:
operator T
operator const T&
operator T&
msvc 2014 CTP:
call_operator.cpp(17): error C2440: 'static_cast': cannot convert from 'call_operator' to ' const int &'
after removal of:
template<typename T>
operator T();
msvc compiles:
call_operator::operator const int &
call_operator::operator const int &
call_operator::operator int &
furthermore, after removal of const in
template<typename T>
operator const T&();
clang-3.6:
call_operator.cpp:26:9: error: ambiguous conversion for static_cast from 'call_operator' to 'int' (void)static_cast<int>(call_operator());
g++-4.9:
operator T
operator const T&
operator T&
msvc 2014 CTP:
call_operator.cpp(16): error C2440: 'static_cast': cannot convert from 'call_operator' to 'int'
In short: Clang is correct (though in one case, for the wrong reason). GCC is wrong in the second case. MSVC is wrong in the first case.
Let's start from static_cast (§5.2.9 [expr.static.cast]/p4, all quotes are from N3936):
An expression e can be explicitly converted to a type T using a
static_cast of the form static_cast<T>(e) if the declaration T t(e);
is well-formed, for some invented temporary variable t (8.5).
The effect of such an explicit conversion is the same as performing
the declaration and initialization and then using the temporary
variable as the result of the conversion. The expression e is used
as a glvalue if and only if the initialization uses it as a glvalue.
Accordingly, the three static_casts here are effectively three initializations:
int t1(call_operator{});
const int & t2(call_operator{});
int & t3(call_operator{});
Note that we rewrote call_operator() as call_operator{} for exposition purposes only, as int t1(call_operator()); is the most vexing parse. There is a small semantic difference between those two forms of initialization, but that difference is immaterial to this discussion.
int t1(call_operator{});
The applicable rule for this initialization is set out in §8.5 [dcl.init]/p17:
if the source type is a (possibly cv-qualified) class type, conversion
functions are considered. The applicable conversion functions are
enumerated (13.3.1.5), and the best one is chosen through overload
resolution (13.3). The user-defined conversion so selected is called
to convert the initializer expression into the object being
initialized. If the conversion cannot be done or is ambiguous, the
initialization is ill-formed.
We proceed to §13.3.1.5 [over.match.conv], which says:
Assuming that “cv1 T” is the type of the object being initialized,
and “cv S” is the type of the initializer expression, with S a
class type, the candidate functions are selected as follows:
The conversion functions of S and its base classes are considered. Those non-explicit conversion functions that are not hidden within S
and yield type T or a type that can be converted to type T via a
standard conversion sequence (13.3.3.1.1) are candidate functions. For
direct-initialization, those explicit conversion functions that are
not hidden within S and yield type T or a type that can be
converted to type T with a qualification conversion (4.4) are also
candidate functions. Conversion functions that return a cv-qualified
type are considered to yield the cv-unqualified version of that type
for this process of selecting candidate functions. Conversion
functions that return “reference to cv2 X” return lvalues or
xvalues, depending on the type of reference, of type “cv2 X” and are
therefore considered to yield X for this process of selecting
candidate functions.
2 The argument list has one argument, which is the initializer
expression. [ Note: This argument will be compared against the
implicit object parameter of the conversion functions. —end note ]
The candidate set, after template argument deduction, is:
operator T() - with T = int
operator const T& () const - with T = int
operator T&() const - with T = int
The argument list consists of the single expression call_operator{}, which is non-const. It therefore converts better to the non-const implicit object parameter of operator T() than to the other two. Accordingly, operator T() is the best match and is selected by overload resolution.
const int & t2(call_operator{});
This initialization is governed by §8.5.3 [dcl.init.ref]/p5:
A reference to type “cv1 T1” is initialized by an expression of type
“cv2 T2” as follows:
If the reference is an lvalue reference and the initializer expression
is an lvalue (but is not a bit-field), and “cv1 T1” is reference-compatible with “cv2 T2,” or
has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be converted to an lvalue of type
“cv3 T3,” where “cv1 T1” is reference-compatible with “cv3 T3”
(this conversion is selected by enumerating the applicable conversion
functions (13.3.1.6) and choosing the best one through overload
resolution (13.3)).
then the reference is bound to the initializer expression lvalue in
the first case and to the lvalue result of the conversion in the
second case (or, in either case, to the appropriate base class
subobject of the object).
Note that this step considers only conversion functions returning lvalue references.
Clang appears to deduce the candidate set as*:
operator const T& () const - with T = int
operator T&() const - with T = int
It's obvious that the two functions are tied on the implicit object parameter since both are const. Further, since both are direct reference bindings, by §13.3.3.1.4 [ics.ref]/p1, the conversion required from the return type of either function to const int & is the identity conversion. (Not Qualification Adjustment - that refers to the conversion described in §4.4 [conv.qual], and is only applicable to pointers.)
However, it appears that the deduction performed by Clang for operator T&() in this case is incorrect‡. §14.8.2.3 [temp.deduct.conv]/p5-6:
5 In general, the deduction process attempts to find template argument
values that will make the deduced A identical to A. However, there
are two cases that allow a difference:
If the original A is a reference type, A can be more cv-qualified than the deduced A (i.e., the type referred to by the
reference)
The deduced A can be another pointer or pointer to member type that can be converted to A via a qualification conversion.
6 These alternatives are considered only if type deduction would
otherwise fail. If they yield more than one possible deduced A, the
type deduction fails.
Since type deduction can succeed by deducing T as const int for operator T&() for an exact match between the deduced type and the destination type, the alternatives shouldn't be considered, T should have been deduced as const int, and the candidate set is actually
operator const T& () const - with T = int
operator T&() const - with T = const int
Once again, both standard conversion sequences from the result are identity conversions. GCC (and EDG, thanks to #Jonathan Wakely for testing) correctly deduces T in operator T&() to be const int in this case*.
Regardless of the correctness of the deduction, however, the tiebreaker here is the same. Because, according to the partial ordering rules for function templates, operator const T& () is more specialized than operator T&() (due to the special rule in §14.8.2.4 [temp.deduct.partial]/p9), the former wins by the tiebreaker in §13.3.3 [over.match.best]/p1, 2nd list, last bullet point:
F1 and F2 are function template specializations, and the function
template for F1 is more specialized than the template for F2
according to the partial ordering rules described in 14.5.6.2.
Thus, in this case, Clang gets the right result, but for (partially) the wrong reason. GCC gets the right result, for the right reason.
int & t3(call_operator{});
There is no fight here. operator const T&(); simply can't possibly be used to initialize a int &. There is only one viable function, operator T&() with T = int, so it is the best viable function.
What if operator const T&(); isn't const?
The only interesting case here is the initialization int t1(call_operator{});. The two strong contenders are:
operator T() - with T = int
operator const T& () - with T = int
Note that the rule regarding ranking standard conversion sequences - §13.3.3 [over.match.best]/p1, 2nd list, 2nd bullet point:
the context is an initialization by user-defined conversion (see 8.5,
13.3.1.5, and 13.3.1.6) and the standard conversion sequence from the return type of F1 to the destination type (i.e., the type of the
entity being initialized) is a better conversion sequence than the
standard conversion sequence from the return type of F2 to the
destination type.
and §13.3.3.2 [over.ics.rank]/p2:
Standard conversion sequence S1 is a better conversion sequence than
standard conversion sequence S2 if
S1 is a proper subsequence of S2 (comparing the conversion sequences in the canonical form defined by 13.3.3.1.1, excluding any
Lvalue Transformation; the identity conversion sequence is considered
to be a subsequence of any non-identity conversion sequence)
cannot distinguish these two, because the conversion necessary to get an int from a const int & is an lvalue-to-rvalue conversion, which is an Lvalue Transformation. After excluding the Lvalue Transformation, the standard conversion sequences from the result to the destination type are identical; nor does any of the other rules in §13.3.3.2 [over.ics.rank] apply.
Thus the only rule that could possibly distinguish between these two functions is again the "more specialized" rule. The question is then whether one of operator T() and operator const T&() is more specialized than the other. The answer is no. The detailed partial ordering rules are rather complex, but an analogous situation is easily found in the example in §14.5.6.2 [temp.func.order]/p2, which labels a call to g(x) as ambiguous given:
template<class T> void g(T);
template<class T> void g(T&);
A quick perusal of the procedure specified in §14.8.2.4 [temp.deduct.partial] confirms that given one template taking a const T& and the other taking a T by value, neither is more specialized than the other**. Thus, in this case, there is no unique best viable function, the conversion is ambiguous, and the code is ill-formed.†
* The type deduced by Clang and GCC for the operator T&() case is determined by running the code with operator const T&() removed.
** Briefly, during the deduction for partial ordering, before any comparison is done, reference types are replaced with the types referred to, and then top-level cv-qualifiers are stripped, so both const T& and T yield the same signature. However, §14.8.2.4 [temp.deduct.partial]/p9 contains a special rule for when both types at issue were reference types, which makes operator const T&() more specialized than operator T&(); that rule doesn't apply when one of the types is not a reference type.
† GCC appears to not consider operator const T&() a viable conversion for this case, but does consider operator T&() a viable conversion.
‡ This appears to be Clang bug 20783.

C++ type qualifiers and equality

Are int& and int the same type? if I use is_same<int,int&>::value i get false but typeid(int).name() == typeid(int&).name() are the same?
secondly the same question for int and const int?
Thirdly int and int*?
I can understand if int and int* are not as one actually stores the address of another object and works differently but I would have thought int& and int are as one is just an alias for another.
Keen to get some good commentary on this.
From Paragraph 5.2.7/4 of the C++11 Standard:
When typeid is applied to a type-id, the result refers to a std::type_info object representing the type of the type-id. If the type of the type-id is a reference to a possibly cv-qualified type, the result of the typeid expression refers to a std::type_info object representing the cv-unqualified referenced type. If the type of the type-id is a class type or a reference to a class type, the class shall be completely-defined.
Thus, typeid(int) and typeid(int&) will give the same result, although the two types are definitely different. Similarly, for the type system int and int const are different types, but the typeid operator ignores the const qualification. From Paragraph 5.2.7/5 of the C++11 Standard:
The top-level cv-qualifiers of the glvalue expression or the type-id that is the operand of typeid are always ignored.
Finally, int and int* are again different types for the type system, and the typeid operator returns different results for them.
Type qualifiers, (const and volatile), create different types. int is a different type from const int.
So do references, pointers, and arrays. For example:
int, int&, int[10] and int* are all different types.
T is a different type from std::remove_reference<T>::type if T is a reference.
The <typeinfo> output of typeid(int).name() is platform-dependent and doesn't have to distinguish between reference/non-reference types. However, the C++ type system definitely distinguishes between T and T&, as you've discovered through type_traits.
std::type_info::name says nothing about identity. If you insist on using typeid to test for identity, try the following:
assert(typeid(T) != typeid(U));
This is using the defined equality comparison operator on the type_info objects. But prepare for disappointment: the above assertion will fail for T = int and U = int& because of §5.2.7/4 (see Andy’s anser).