Our code is multi-platform. I use MSVC and my colleagues use Clang or GCC.
One of our classes has a pointer conversion, which always returns non-nullptr. I found a problem due to to accidentally using it in boolean expressions, which would always return true. So, I deleted the boolean conversion operator.
This worked perfectly for me (and actually revealed ten other places in our program where we were doing this), but my colleagues could no longer compile due to ambiguous overloads.
Here's the situation:
class Thingy
{
public:
operator const double * () const
{
return xyz;
}
operator bool () const = delete;
private:
double xyz[3];
};
void func(const double *d)
{
}
void func(unsigned int i)
{
}
int main()
{
Thingy a;
func(a);
}
On MSVC, func(const double *d) is called, which I would expect. However, on GCC, it fails as follows:
<source>: In function 'int main()':
<source>:27:9: error: call of overloaded 'func(Thingy&)' is ambiguous
func(a);
^
<source>:16:6: note: candidate: 'void func(const double*)'
void func(const double *d)
^~~~
<source>:20:6: note: candidate: 'void func(unsigned int)'
void func(unsigned int i)
^~~~
Compiler returned: 1
If you take out the operator bool () const = delete;, it works fine on GCC, but you then don't have protection from the following:
Thingy b /* = callSomeFunction()*/;
if (!b)
{
// whoops! never enters here! Would be nice for this to be a compiler error.
}
else
{
// do something with the valid Thingy
}
We think that on GCC/Clang the ambiguity is between calling func(const double*) with the conversion to const double* and calling func(unsigned int) with a conversion to bool, even though the conversion to bool is explicitly invalid.
Why is this the case? Why does MSVC allow it? Is there any way we can get the desired effect, other than perhaps #ifdef _MSVC_VER?
The simplest solution would be to declare the operator bool as explicit. Used as a condition in a if, while or for, the explicit operator would be called, but ignored for implicit conversions on function calls.
http://coliru.stacked-crooked.com/a/78dd309602295e79
Related
The following won't compile:
#include <memory>
class A;
bool f() {
std::shared_ptr<A> a;
return a;
}
int main()
{
f();
return 0;
}
and fails with:
Compilation failed due to following error(s).main.cpp: In function ‘bool f()’:
main.cpp:13:12: error: cannot convert ‘std::shared_ptr’ to ‘bool’ in return
return a;
What could be the reasoning of the standard (I presume) not allowing an implicit conversion here?
Because the user-defined operator for converting an std::shared_ptr to bool is explicit:
explicit operator bool() const noexcept;
Note that an implicit conversion to bool in the condition of an if statement – among others – still happens even with an explicit user-defined conversion operator to bool :
std::shared_ptr<int> ptr;
if (ptr) { // <-- implicit conversion to bool
}
That is, you don't need to write static_cast<bool>(ptr) in the condition of an if statement.
Is there a way to disable conversion operators? Marking them "= delete" messes up other things.
Consider the following code:
class Foo
{
public:
Foo() :mValue(0) {}
~Foo() = default;
Foo(int64_t v) { mValue = v; }
Foo(const Foo& src) = default;
bool operator==(const Foo& rhs) { return mValue == rhs.mValue; }
/* after commenting these lines the code will compile */
operator int() const = delete;
operator int64_t() const = delete;
private:
int64_t mValue;
};
int main()
{
Foo foo1(5);
Foo foo2(10);
bool b1 = (foo1 == foo2);
bool b2 = (foo1 == 5);
}
This won't compile because gcc complains that the == operator is ambiguous:
test.cc: In function ‘int main()’:
test.cc:25:21: error: ambiguous overload for ‘operator==’ (operand types are ‘Foo’ and ‘int’)
bool b2 = (foo1 == 5);
^
test.cc:25:21: note: candidates are:
test.cc:25:21: note: operator==(int, int) <built-in>
test.cc:25:21: note: operator==(int64_t {aka long int}, int) <built-in>
test.cc:10:10: note: bool Foo::operator==(const Foo&)
bool operator==(const Foo& rhs) { return mValue == rhs.mValue; }
^
However, after commenting the conversion operators, the code will compile and run nicely.
The first question is: why do the deleted conversion operators create an ambiguity for the == operator? I thought they should disable implicit Foo -> int conversions but they seem to affect int -> Foo conversions which does not sound logic to me.
Second one: is there a way to mark the conversion operators deleted? Yes, by not declaring them - but I'm looking for a way that anyone in the future will see that those conversions are disabled by design.
Any use of a deleted function is ill-formed (the program will not
compile).
If the function is overloaded, overload resolution takes place first,
and the program is only ill-formed if the deleted function was
selected.
In you case programm can't select conversion becouse you have 3 variant
int -> Foo
Foo -> int
Foo -> int64
Second question:
you can leave it as it is, but always use explicit conversion for int
bool b2 = (foo1 == Foo(5));
Here's what I think is the crux of the matter:
[dcl.fct.def.delete]:
A program that refers to a deleted function implicitly or explicitly, other than to declare it, is ill-formed.
...
A deleted function is implicitly an inline function ([dcl.inline]).
[class.member.lookup/4]:
If C contains a declaration of the name f, the declaration set
contains every declaration of f declared in C that satisfies the
requirements of the language construct in which the lookup occurs.
Even if you delete the function, you still declare it. And a declared function will participate in overload resolution. It's only when it's the resolved overload, that the compiler checks if it's deleted.
In your case, there is an obvious ambiguity when those function declarations are present.
Consider following program it compiles and runs fine:
#include <iostream>
#include <string>
using std::string;
struct BB
{
// generic cast
template<typename T>
operator T() const
{
std::cout<<"Generic cast\n";
return 0;
}
// string cast
operator string() const
{
std::cout<<"string cast\n";
return string("hello");
}
};
int main()
{
BB b;
string s = b; // copy constructor
}
But If I slightly change the main() function's code in like following:
int main()
{
BB b;
string s;
s = b;
}
Compiler give following error message (See live demo here)
[Error] ambiguous overload for 'operator=' (operand types are 'std::string {aka std::basic_string<char>}' and 'BB')
Why this call is ambiguous? What is the reason behind that? It looks like there are so many overloaded operator= like one for char, one for char*, one for const char* etc. That's the above program puts compiler into ambiguity.
Your problem is your template conversion operator.
template<typename T>
operator T() const
{
std::cout << "Generic cast\n";
return 0;
}
Allows BB to be converted to anything. Because of that all of the overloads of std::string::operator= that take a different type can be considered. Since they are all valid there is no way to resolve the ambiguity and you get the error.
If you removed the template conversion then it will compile. The template conversion could also be marked as explicit and then you can use a static_cast to the type you want.
You call operator =, but it would be the same if it were a regular function:
void f(int x);
void f(const string &y);
int main() {
BB b;
f(b);
return 0;
}
Since BB can be cast in either int or string, the compiler has no idea which f function to call.
The only reason why your first example works is because the copy constructor is called there, and it only takes const string& as an argument, so there's no multiple choices.
Following code compiles fine without any warnings (with default options to g++). Is there a flag that we can use to ask g++ to emit warnings in such cases?
void foo(bool v) {
}
void bar() {
foo("test");
}
I like to try clang -Weverything and pick the warnings that pop out:
void foo(bool) {}
void bar() {
foo("test");
}
void baz() {
foo(nullptr);
}
int main() {}
main.cpp:5:7: warning: implicit conversion turns string literal into
bool: 'const char [5]' to 'bool' [-Wstring-conversion] foo("test");
main.cpp:8:9: warning: implicit conversion of nullptr constant to
'bool' [-Wnull-conversion]
foo(nullptr);
Unfortunately, neither -Wstring-conversion nor -Wnull-conversion is supported by g++. You could try and submit feature request / bug report to gcc.
If I really wanted to prevent such a function being passed a pointer, I would do this in C++11;
void foo(bool);
template<class T> void foo(T *) = delete;
void bar()
{
foo("Hello");
}
which will trigger a compiler error.
Before C++11 (not everyone can update for various reasons) a technique is;
void foo(bool);
template<class T> void foo(T *); // note no definition
void bar()
{
foo("Hello");
}
and then have the definition of foo(bool) somewhere (in one and only one compilation unit within your build). For most toolchains that use a traditional compiler and linker (which is most toolchains in practice, including most installations of g++) the linker error is caused by foo<char const>(char const*) not being defined. The precise wording of the error is linker dependent.
Note that the errors can be deliberately circumvented by a developer. But such techniques will stop accidental usage.
If you want to allow any pointer except a char const * being passed, simply declare void foo(const char *) as above and don't declare the template.
g++ compiler complains about:
error: no matching function for call to ‘AddressSpace::resolve(ClassOne&, ClassTwo*&, ClassThree&) const’
note: candidates are: bool AddressSpace::resolve(ClassOne&, ClassTwo*, ClassThreer) <near match>
The code causing this error is
void Class::handler(ClassOne& objOne, ClassTwo& objTwo,
ClassThreer objThree) {
obj.getAddressSpaceObj().resolve(objOne, objTwo.pointer, objThree);
}
I digged into the code and found this error is caused by the reference type returned by getOtherObj() . I make it to return a const reference to the AddressSpace object in the class definition, see
const AddressSpace &getAddressSpaceObj(){
return addressSpace;
}
After I change this definition to return a normal reference,
AddressSpace &getAddressSpaceObj(){
return addressSpace;
}
the compiler doesn't complain about it any more. I wonder why this error is declared as parameter mismatching error? Why compiler didn't copy content as the parameters of function call but passed them as references?
If resolve does not have a const specifier then you can not call it on a const reference, so that would be consistent with changing it to be being non-const and having it now work. Here is a really trivial example:
#include <iostream>
class A
{
public:
void someFuncA() {};
void someFuncB() const {} ;
} ;
int main()
{
A
a1 ;
const A &aRef = a1 ;
a1.someFuncA() ;
// Below won't work because aRef is a const & but someFuncA() not const
//aRef.someFuncA() ;
// Below will work since someFuncB() is const
aRef.someFuncB() ;
}
Just for completeness sake, if you uncomment aRef.someFuncA() then the error you will receive will be similar to this:
19:19: error: no matching function for call to 'A::someFuncA() const'
19:19: note: candidate is:
6:12: note: void A::someFuncA() <near match>