C++17 conditional (ternary) operator inconsistency between MSVC and Clang/GCC - c++

The following code compiles under Clang/GCC under the C++17 standard, but does not compile under MSVC with -std:C++17 /Zc:ternary.
struct CStringPtr
{
const char *m_pString = nullptr;
CStringPtr() = default;
CStringPtr( const char *pString ) : m_pString( pString ) { }
operator const char *() const { return m_pString; }
};
int main( int argc, char ** )
{
bool b = !!argc;
const char *X = b ? CStringPtr( "inside" ) : "naked";
const char *Y = b ? "naked" : CStringPtr( "inside" );
CStringPtr Z = b ? CStringPtr( "inside" ) : "naked";
CStringPtr W = b ? "naked" : CStringPtr( "inside" );
// Silence unused-variable warnings.
return X && Y && Z && W;
}
Link to godbolt's compiler explorer with all three: https://godbolt.org/z/6d5Mrjnd7
MSVC emits an error for each of those four lines:
<source>(19): error C2445: result type of conditional expression is ambiguous: types 'const char [6]' and 'CStringPtr' can be converted to multiple common types
<source>(19): note: could be 'const char *'
<source>(19): note: or 'CStringPtr'
Whereas Clang/GCC each call the CStringPtr constructor for the naked string for all four of those cases.
In the MSVC /Zc:ternary documentation they claim that the flag enables standards-compliant resolution of the ternary operator, which implies that either there's a bug in MSVC's implementation or Clang/GCC are not standards-compliant here.
The one other note here is that the MSVC docs mention an exception in this exact case with respect to the const char * type being used:
An important exception to this common pattern is when the type of the operands is one of the null-terminated string types, such as const char*, const char16_t*, and so on. You can also reproduce the effect with array types and the pointer types they decay to. The behavior when the actual second or third operand to ?: is a string literal of corresponding type depends on the language standard used. C++17 has changed semantics for this case from C++14.
So, is MSVC non-compliant under C++17 rules? Or are Clang/GCC?

This is Core issue 1805, which changed ?: to decay arrays and functions to pointers before testing whether the other operand can be converted to it. The example in that issue is basically the one in your question:
struct S {
S(const char *s);
operator const char *();
};
S s;
const char *f(bool b) {
return b ? s : ""; // #1
}
One might expect that the expression in #1 would be ambiguous, since
S can be converted both to and from const char*. However, the
target type for the conversion of s is const char[1], not const char*, so that conversion fails and the result of the
conditional-expression has type S.
It appears that neither GCC nor Clang implements that issue resolution, so they are still testing whether CStringPtr can be converted to an array.
If you manually decay the string literals with a unary +, everyone rejects the example as ambiguous.

Related

Why can we say ' char *c = "Hello"; ' in C++

When I do-
#include <iostream>
int main(){
char *p = "Hello";
}
it works fine, but doing
#include <iostream>
int main(){
char *p = 'H';
// OR
int *x = 5;
}
gives an error: invalid conversion from 'char' to 'char*' [-fpermissive] / 'int' to 'int*'
The issue here is that C++ is a strongly typed language. You have to make sure the type on the right of an = is the same as the type on the left (or there is some well defined conversion that allows the compiler to convert between types).
So we need to know what types literals are: Using a double quote creates a string literal of type char const[] while using a single quote creates a character literal of type char const.
You will have to bear with me on the const part. That makes the discussion much more complicates so we will gloss over it initially.
We also need to know that when arrays are used in expressions they very easily decay into pointers. So it most situations the type of char const[] will decay into char const*.
This should work:
char const* p = "Hello"; // This is valid.
// Both left and right sides have the same type.
// After you consider the array decay into a pointer.
On the other hand
char const* p = 'H'; // The type on the right is `char const'
// While the type on the right has a pointer in it.
Now there are some automatic conversions at play here.
In the original C++03 the compiler was allowed to auto convert string literals from char const* to char*. This is a relic from the old C language that was not as strict on type checking as C++ is now. This allows for this:
char* p = "Hello"; // Because there is an auto conversion
// the compiler is allowed to make
// the conversion from one type to another
Note in later versions of C++ this conversion has been deprecated. SO the compiler will warn you that this is dangerous (because you have removed the const from the type you are allowed to modify it, but the underlying object can't be modified so if you try it will blow up the program).
So why can you assign char const to a char?
char x = 'X';
Here you are copying the original object char const into an object of type char this is perfectly valid. You can not alter or chance the literal but you are allowed to make copies of it. So you can easily remove the outer const in an assignment expression.
char const* const y = "Hello";
char const* z = y; // We remove the outer const
// from a pointer. But the inner
// const (of the object being pointed
// at) can not be removed so easily.
// This works because z is allowed to
// to be changed but hold a value a pointer
// to a value that can not be changed.
Looking at your comment:
#include <iostream>
void test(char *str)
{
std::cout << str << std::endl;
}
int main()
{
test("Hello"); // This is allowed in C++
// Deprecated still means valid.
// Just not a good idea
// There is a allowed conversion from
// char const* to char* for string literals.
char const* x = "test";
test(x); // This is NOT allowed.
// You can not cast away this const.
}
Note: Technically a string literal is char const[]. ie an array of const char. BUT when used in an expression arrays very easily decay into pointers and thus it is sometimes simpler to think of them as char const* but this thinking is abstract and you should know the underlying exact type.

Zero in double vs char* ambiguity

I have a handy class in C++ that can be initialized either with an double or with a char*. Everything works as expected except one case when the argument is zero.
struct Var {
Var (double src) {}
Var (char *src) {}
};
int main(int argc, char* argv[]) {
Var A = 123;
Var B = "abc";
Var C = 0; <- Error: conversion from 'int' to 'Var' is ambiguous
}
Previously I used int instead of double, and it was fine for some reason.
How can this be fixed?
PS: I know I can use (double)0 or just 0.0, but is there a way to allow just 0 to be accepted as double?
You could eliminate the ambiguity by adding a third constructor which takes an int and delegates to the double version (requires C++11):
struct Var {
Var (double src) {}
Var (const char *src) {}
// ^^^^^ should be const
Var (int src) : Var {static_cast<double>(src)} {}
};
However, this could be solved easier by changing your char* constructor to take a std::string instead and using direct initialization.
In C++, the literal 0 has type int. You cannot change anything about it being an integer, without adding at least a decimal point. (no actual need to write digits after the point though)
To answer you implicit question regarding why the call was unambiguous with 123 but ambiguous with 0:
When you initialized A, it was fine because there is no implicit conversion from an int to a pointer (char*, in this case).
When you initialized C using an int the second time, you used 0, the literal 0 can be implicitly converted to a pointer (some legacy behaviour, before C++11 offered nullptr literal).
So, in a nutshell:
int * a = 0; // legal;
int * b = 2; // ILLEGAL;
When initializing C one conversion is needed to give the argument to any of the two available constructors (and both conversions have the same ranking), the call is ambiguous.
To make it unambiguous, you should either cast your int literal to a double ((double)0), use a double literal (0.), or add a constructor taking an int.
With this last solution, the constructor taking an int is an exact match, so it is a better match that another overload requiring a conversion (which is why it would be selected).
Edit. Please note that TartanLlama answer should be accepted here as it is a best C++ practice: the constructor taking an C-style string should take a std::string instead. Since there is an implicit conversion from C-style string to std::string, the call will work with little change:
Var B("abc");
But you will isolate your class interface from pointers details.

const correctness and parameters to functions with structs containing void*

I'm not going to say I fully understand the idea of const correctness but let's at least say that I understand it. So, when I encountered this, I was/am stumped. Can someone please explain this to me. Consider the following code:
#include <iostream>
struct Test {
int someInt;
void * pSomething;
};
void TestFunc(Test * const pStruct) {
pStruct->someInt = 44;
pStruct->pSomething = "This is a test string"; // compiler error here
}
int main() {
Test t;
TestFunc(&t);
return 0;
}
At the point I've annotated with a comment I get this error from gcc (4.5.3 for cygwin):
foo.cpp:10:24: error: invalid conversion from `const void*' to `void*'
It's apparently something to do with the fact that the struct contains a void* because some experimentation revealed that changing the struct to:
struct Test {
int someInt;
char * pSomething;
};
produces a warning as opposed to an error. Also, leaving the structure unchanged but modifying this line to include the following cast compiles the code without warning:
pStruct->pSomething = (void*)"This is a test string"; // no error now
What I don't understand, given my understanding of const correctness, is why is the compiler emitting this error, "invalid conversion from ‘const void*’ to ‘void*’" at all? Since the const modifier on the function definition makes it so that the pointer is constant but what it points to is not, why should this be a problem? I'm assuming that there is some sort of implicit cast happening, as originally written, since the string literal would be something like const char * that must be converted to void *. That notwithstanding, the question remains since what pStruct points to is not constant.
For reference, I read up on const correctness here and here.
The error you are observing has absolutely nothing to do with const qualifier used in function declaration, or with any const qualifiers you explicitly used in your code.
The problem is the same as in the following minimal example
void *p = "Hello";
which suffers from the same error.
In C++ language the type of string literal is const char [N]. By the rules of const correctness it is convertible to const void *, but it is not convertible to void *. That's all.
A more formally correct error message would be "Cannot convert from const char [22] to void *", or "Cannot convert from const char * to void *", but apparently the inner workings of the compiler perform the conversion to const void * first (under the hood) and then stumble on conversion to void *, which is why the error message is worded that way.
Note that const correctness rules of C++ language used to include an exception that allowed one to implicitly convert string literals to char * pointers. This is why
char *p = "Hello";
compiles with a mere warning, even though it violates the rules of const correctness just like the previous example. That exception applied only to conversions to char * type, not to void * type, which is why the previous example produces an error. This special conversion has been deprecated in C++03 and removed from the language in C++11. This is why the compiler issues a warning about it. (If you switch your compiler to C++11 mode it will become an error.)
First of all, your test class and function just make things unnecessarily complex. In particular, the error has nothing to do with the const in Test * const pStruct, because this only means that pStruct must not be made to point to anything else. After all, in your own words:
the const modifier on the function definition makes it so that the
pointer is constant but what it points to is not
Here is a simplified piece of code to reproduce the problem:
int main() {
void *ptr = "This is a test string"; // error
}
As for your question,
What I don't understand, given my understanding of const correctness,
is why is the compiler emitting this error, "invalid conversion from
‘const void*’ to ‘void*’" at all? Since the const modifier on the
function definition makes it so that the pointer is constant but what
it points to is not, why should this be a problem?
Because a string literal is a char const[], "decaying" to a char const *, and the conversion to a non-constant pointer would lose the const qualifier.
This does not work for the same reason the following won't:
int main() {
int const *i; // what's pointed to by i shall never be modified
void *ptr = i; // ptr shall modify what's pointed to by i? error!
}
Or more precisely,
int main() {
int const i[22] = {}; // i shall never be modified
void *ptr = i; // ptr shall modify i? error!
}
If this was not an error, then you could use ptr to implicitly bypass the const qualifier of i. C++ simply does not allow this.
Finally, let's look at this piece of code:
pStruct->pSomething = (void*)"This is a test string"; // no error now
Again, this can be simplified and reproduced with int rather than char so as not to obfuscate the real issue:
int main() {
int const i[22] = {}; // i shall never be modified
void *ptr = (void*)i; // ptr shall modify i? ok, I'll just shut up
}
You should not use C-style casts in C++. Use one of static_cast, reinterpret_cast, dynamic_cast and const_cast to make it clear which kind of conversion should be enforced.
In this case, you'd have seen the trouble it takes to "shut up" the compiler:
int main() {
int const i[22] = {};
void *ptr = const_cast<void*>(reinterpret_cast<void const *>(i));
}
And, of course, even though this may compile without a warning, the behaviour of the program is undefined because you must not use const_cast to cast away the constness of an object originally initialised as constant.
Edit: I forgot about the whole char * C compatibility business. But this is covered in the other answers already and, to my best understanding, does not render anything in my answer incorrect.
First of all, using C style casts will break const correctness. That is the only reason that your cast "works". So don't do it. Use reinterpret_cast, which (should, I didn't test it) get you a similar error to the one you are seeing.
So "This is a test string" is a const char*. If you reference it as a void*, something could modify the contents of void* later. If you make it so you can mess with the contents of that, you're no longer const correct.
And you COULD if there was no error, shown below.
int main() {
Test t;
TestFunc(&t);
reinterpret_cast<char*>(t.pSomething)[0]='?';
return 0;
}
"blah" is an array of 5 char const. In C++11 it converts implicitly to char const*. In C++03 and earlier the literal also converted implicitly to char*, for C compatibility, but that conversion was deprecated, and it was removed in C++11.
Setting
void* p = "blah"; // !Fails.
you get the conversion sequence char const[5] → char const* → void*, where the last one is invalid and yields an error.
Setting
char* p = "blah"; // Compiles with C++03 and earlier.
with C++11 you get the same conversion as for void*, and an error, but with C++03 and earlier, since the source is a literal string you get char const[5] → char*.

operator wchar_t**() failing to provide implicit conversion to const wchar_t ** - why?

I have a problem understanding why a certain implicit conversion is not working as I expect it. I have the following class
ref class ManagedWStringArrayWrapper
{
wchar_t** m_pointer;
public:
operator wchar_t**()
{
return m_pointer;
}
};
and I thought this would implicitly convert to const wchar_t ** as well - but it doesn't. Can someone tell me why?
Conversion from T** to T const** is not as intuitive as you might expect — in fact, it's given as an example in the standard itself as const-incorrect code.
The example given is:
#include <cassert>
int main() {
char* p = 0;
//char const** a = &p; // not allowed, but let's pretend it is
char const** a = (char const**)&p; // instead force the cast to compile
char const* orig = "original";
*a = orig; // type of *a is char const*, which is the type of orig, this is allowed
assert(p == orig); // oops! char* points to a char const*
}
Study the above scenario for a while and it will become clear that this is not the same as a conversion T* → T const*. Not at all!
I asked the same question in a blog post, and the FAQ has an entry on it too.
Because those are different things. If you look, for instance, at the C++ libraries, you'll find that there are often two functions for doing the same thing, one operating on const pointers and the other on non-const. Example.
But you can easily add an explicit operator const wchar_t**.

c++ error: operator []: 2 overloads have similar conversions

template <typename T>
class v3 {
private:
T _a[3];
public:
T & operator [] (unsigned int i) { return _a[i]; }
const T & operator [] (unsigned int i) const { return _a[i]; }
operator T * () { return _a; }
operator const T * () const { return _a; }
v3() {
_a[0] = 0; // works
_a[1] = 0;
_a[2] = 0;
}
v3(const v3<T> & v) {
_a[0] = v[0]; // Error 1 error C2666: 'v3<T>::operator []' : 2 overloads have similar conversions
_a[1] = v[1]; // Error 2 error C2666: 'v3<T>::operator []' : 2 overloads have similar conversions
_a[2] = v[2]; // Error 3 error C2666: 'v3<T>::operator []' : 2 overloads have similar conversions
}
};
int main(int argc, char ** argv)
{
v3<float> v1;
v3<float> v2(v1);
return 0;
}
If you read the rest of the error message (in the output window), it becomes a bit clearer:
1> could be 'const float &v3<T>::operator [](unsigned int) const'
1> with
1> [
1> T=float
1> ]
1> or 'built-in C++ operator[(const float *, int)'
1> while trying to match the argument list '(const v3<T>, int)'
1> with
1> [
1> T=float
1> ]
The compiler can't decide whether to use your overloaded operator[] or the built-in operator[] on the const T* that it can obtain by the following conversion function:
operator const T * () const { return _a; }
Both of the following are potentially valid interpretations of the offending lines:
v.operator float*()[0]
v.operator[](0)
You can remove the ambiguity by explicitly casting the integer indices to be unsigned so that no conversion is needed:
_a[0] = v[static_cast<unsigned int>(0)];
or by changing your overloaded operator[]s to take an int instead of an unsigned int, or by removing the operator T*() const (and probably the non-const version too, for completeness).
In simple terms: the compiler doesn't know whether to convert v to const float* and then use the operator[] for a pointer, or to convert 0 to unsigned int and then use the operator[] for const v3.
Fix is probably to remove the operator[]. I can't think of anything it gives you that the conversion operator to T* doesn't already. If you were planning to put some bounds-checking in operator[], then I'd say replace the conversion operators with getPointer functions (since in general you don't want to implicitly convert a safe thing to an unsafe thing), or do what std::vector does, which is that users get a pointer with &v[0].
Another change that lets it compile, is to change operator[] to take an int parameter instead of unsigned int. Then in your code, the compiler unambiguously chooses the interpretation with no conversion. According to my compiler, there is still no ambiguity even when using an unsigned index. Which is nice.
When you the compiler compiles the following
v[0]
it has to consider two possible interpretations
v.operator T*()[0] // built-in []
v.operator[](0) // overloaded []
Neither candidate is better than the other because each one requires a conversion. The first variant requires a user-defined conversion from v3<T> to T*. The second variant requires a standard conversion from int (0 is int) to unsigned int, since your overloaded [] requires an unsigned int argument. This makes these candidates uncomparable (neither is clearly better by C++ rules) and thus makes the call ambuguous.
If you invoke the operator as
v[0U]
the ambiguity will disappear (since 0U is already an unsigned int) and your overloaded [] will be selected. Alternatively, you can declare your overloaded [] with int argument. Or you can remove the conversion operator entirely. Or do something else to remove the ambiguity - you decide.
It is your type conversion operator that is the culprit. v transformed to a float pointer. Now there are two operator []s possible, one is the built in subscript operator for float and the other being the one you defined on v, which one should the language pick, so it is an ambiguity according to ISO.
Remember a class is a friend of itself:
v3(const v3<T> & v)
{
_a[0] = v._a[0];
_a[1] = v._a[1];
_a[2] = v._a[2];
}
When copy something of the same type you are already exposed to the implementation details. Thus it is not a problem to access the implementation directly if that is appropriate. So from the constructor you can access the object you are copying directly and see its member '_a'.
If you want to know the original problem:
The literal '1' in the context 'v[1]' is an integer (this is a synonym of signed integer). Thus to use the operator[] the compiler technically is required to insert a conversion from int to unisgned. The other alternative is to use the operator*() to get a pointer to the internal object and then use the [] operator on the pointer. Compiler is not allowed to make this choice and error out:
Compiler options:
_a[1] = v[1];
// Options 1:
_a[1] = v.operator[]((unsigned int)1);
// Options 2:
_a[1] = v.operator*()[1];
To make it unabigious you can use an unsigned literal;
_a[1] = v[1u];
In the long run it may be worth making this easier for the user.
Convert the operator[] to use int rather than unsigned int then you will get exact matches when integer literals (or you can have two sets of operator[]. One that uses int and one that uses unsigned int).
I didn't see it untill James McNellis posted the full error message, but the ambiguity is not between the two v3::operator[]() functions as it appears to be.
Instead, since there is no exact match between argument types, the compiler can't decide whether to:
a) Use v3::operator[](unsigned int) const, thereby converting the int argument to unsigned, or
b) Use the v3::operator const T*() const conversion followed by the built-in array indexing operator.
You can avoid this by making the operator[] arguments int's rather than unsigned ints. But a better solution would be to avoid an implicit conversion to T* and instead provide a Data() function that did that explicitly.
I had this same issue: I resolved it simply making the typecast operator explicit.
The const version doesn't modify anything. The non-const version allows you to assign things using array notation (v[3] = 0.5;).