Why is the following allowed to be compiled in C++?
#include<iostream>
using namespace std;
class mytest
{
public:
operator int()
{
return 10;
}
operator const int()
{
return 5;
}
};
int main()
{
mytest mt;
//int x = mt; //ERROR ambigious
//const int x = mt; //ERROR ambigious
}
Why does it make sense to allow different versions (based on constness) of the conversion operator to be compiled when their use always results in ambiguity?
Can someone clarify what I am missing here?
For conversion they're ambiguous; but you might call them explicitly. e.g.
int x = mt.operator int();
const int x = mt.operator const int();
I believe that in the strictest sense, even if it doesn't really make much sense for const, this is legitimate.
There is a difference between a function declaration and a function type, and they do not have the same constraints.
Function declarations may not differ in only their return type or (since C++17) exception specification. However, no such thing is said about the function type (to my knowledge).
The standard [class.conv.fct] decribes conversion functions as having such-and-such form (three alternatives listed), all of which do not look like normal function declarations, in particular they very obviously have no return type.
It does state, that the function type is "function taking no parameter returning conversion-type-id", but nowhere is it mentioned that conversion function declarations have any such thing as a return type. On the contrary, the three alternative forms listed very clearly do not have a return type.
Since conversion functions don't have a return type (... in their declaration), it cannot conflict. So, I guess, in the strictest, most pedantic sense, it's even kinda "legal", whether it makes sense or not.
If you think about it, then it somehow has to be legal, too. A class may very well have more than one conversion function to different things (not just differing by const). Such code does exist, and it sometimes makes a lot of sense going that way.
You might for example have a class File that converts to either a string (the filename) or to a handle_t (the operating system handle) in case you want to use some OS-specific or exotic function that your wrapper class doesn't directly support (think writev, tee, or epoll?) It's certainly something you'd expect to work!
If, however, we treated conversion functions as "just functions", then they'd only differ in their return type, which would render the declarations illegal. So... that wouldn't work.
why does it make sense to allow different version(based on constness) of conversion operator (to be compiled) when their use always result in ambiguity;
It usually makes no sense (apart from highly artificial use cases) and a compiler could warn you about it:
prog.cc:12:25: warning: type qualifiers ignored on function return type [-Wignored-qualifiers]
operator const int()
^
I come to the conclusion, that it's not explicitly allowed to write conversation operators that only differ by constness of their return value. It is too expensive for the compilation process to explicitly disallow it.
Remember that (member) functions that only differ by their return type
class mytest
{
int f();
const int f();
};
are forbidden:
error: ‘const int mytest::f()’ cannot be overloaded
It's just that conversion operators start with operator that makes the difference.
Related
const int test();
int test(){
return 5;
}
int main(){
return 0;
}
Above does not compile in C++ with this error message:
error: ambiguating new declaration of 'int test()'
However it does compile fine in C. Knowing these are 2 very different languages, I was wondering if there's a specific feature in C++ which requires it
to have const return types in both the definition and declaration?
I was wondering if there's a specific feature in C++ which requires it
to have const return types in both the definition and declaration?
No. Actually, it's the other way around. There is a specific feature in C that drops that requirement.
From C documentation of function declarations # cppreference.com:
The return type cannot be cvr-qualified: any qualified return type is adjusted to its unqualified version for the purpose of constructing the function type.
In your C example, even though you think you declared const int test();, the const qualification is dropped and you actually declared int test();. Hence declaration and definition match.
C++ does not drop cv-qualification from return types.
Yes, cv-qualified return type is different from a non-qualified return type, so those declarations conflict.
Note that it isn't ever useful to return a cv-qualified non-class object because the qualifiers will have no effect on the returned value. For classes, it does affect the type of the value and thus affects which member functions can be called (but cv qualified class return types are still rarely useful, if ever).
C++ has references.
In C you're always going to get a copy of the return value, so it doesn't matter so much if it's const or not.
int x;
x = test();
In C++ you can capture the return value with a reference, and it makes a big difference if the reference is const or not.
int &x = test();
const int &y = test();
So the compiler needs to know exactly what you're returning.
Consider:
template<typename T>
struct Prop
{
T value;
operator T() { return value; }
};
int main()
{
Prop<float> p1 { 5 };
Prop<std::vector<float>> p2 { { 1, 2, 3 } };
float f1 = p1; // Works fine
float f2_1_1 = p2.value[0]; // Works fine
float f2_1_2 = p2[0]; // Doesn't compile
return 0;
}
Why doesn't the line marked as such compile? Shouldn't it perform implicit conversion using the supplied conversion operator into std::vector<>, so that the [] can be found?
There are (many) other questions on this site that ask variations on this question, but I couldn't find one that I think applies here. Does it have to do with std::vector being a template?
Implicit conversions are not considered for objects of member function calls, including the subscript operator overload.
Consider the consequences if that were allowed: Every single time any undeclared member function is called like this, the compiler would have to figure out all the types that the object can be converted to (note that any other otherwise unrelated type could have a converting constructor), and check if that has declared the missing member function. Not to mention how confusing that would be for the reader of the code (the conversion might be obvious in your conversion operator case, but not in the converting constructor case, and as far as I know, they are not otherwise treated differently).
So is there a notationally convenient way to get Prop to behave the way I want
You would have to define a member function for each of the member function of vector that you want to pass pass through transparently. Here is the subscript operator as an example:
auto operator[](std::size_t pos) {
return value[pos];
}
auto operator[](std::size_t pos) const {
return value[pos];
}
The problem is of course that all wrapped member functions must be explicitly declared. Another problem is arguments whose type depend on T. For example, vector::operator[] uses vector::size_type, which might not be defined for all T that you might use (certainly not for float). Here we make a compromise and use std::size_t.
A less laborious way of creating such "transparent" wrapper is inheritance. A publicly inheriting template would automatically have all the member functions of the parent and be implicitly convertible to it and can be referred by pointers and references of parent type. However, the transparency of such approach is a bit problematic mainly because ~vector is not virtual.
Private inheritance allows same wrapping as your member approach, but with much nicer syntax:
template<typename T>
struct Prop : private T
{
using T::operator[];
using T::T;
};
Note that inheritance approach prevents you from using fundamental types as T. Also, it makes the implicit conversion impossible (even with conversion operator) , so you cannot use Prop as T in free functions that expect T.
PS. Note that your conversion operator returns a value, so the vector must be copied, which may be undesirable. Consider providing reference versions instead:
operator T&&()&& { return *this; }
operator T&()& { return *this; }
operator const T&() const& { return *this; }
the same way that any of the
p2.size();
p2.begin();
p2.push_back(24);
// etc.
don't make sense to compile
also
p2[0];
which is equivalent with
p2.operator[](0);
doesn't make sense to compile\
If you want this behavior (i.e. for Prop<T> to borrow T members) there is a C++ proposal by Bjarne to add the dot operator to the language. I would work the same way that operator -> works for smart pointers. AFAIR it had a lot of controversy and so I wouldn't hold my breath for it.
A bit of background for the operator dot proposal—Bjarne Stroustrup
Operator Dot (R3) - Bjarne Stroustrup, Gabriel Dos Rei
Smart References through Delegation: An Alternative to N4477's Operator Dot - Hubert Tong, Faisal Vali
Alternatives to operator dot - Bjarne Stroustrup
I am on the task to migrate the concept of error handling in a C++ class library. Methods that previously simply returned bool (success/fail) shall be modified to return a Result object which conveys a machine readable error code and a human readable explanation (and some more which does not matter here).
Walking through thousands of lines of code is error prone, therefore I try to get the best support from the compiler for this task.
My result class has - among other member methods - a constructor that constructs the result from a code and an assignment operator for the code:
class Result
{
public:
typedef unsigned long ResultCode;
explicit Result(ResultCode code); // (1)
Result& operator=(ResultCode code); // (2)
};
Remark: I would usually use an enum class for ResultCode which would solve my problems, but this is not an option. This is because the major design objective was to use Result in different libraries, each of which shall define its own set of result codes without requiring one big header file that defines all possible result codes for all libraries. In fact, each class shall be able to define local result codes so that the list of possible result codes can be obtained from the classes header. Thus the codes cannot be enumerated in Result, they must be defined by the classes using the Result class.
To avoid implicit conversions on
return true;
Statements in the client code, the constructor has been declared explicit. But in nesting method calls, another problem occurs. Say, I have a method
bool doSomething()
{
return true;
}
Which I am using in a function that returns a Result object. I want to forward result codes of nested calls
Result doSomethingElse
{
Result result = doSomething();
return result;
}
With the current implementation of Result's assignment operator, this is not going to give me a compiler error - the boolean return value of doSomething() is implicitly converted to unsigned long.
As I have read in the C++ documentation, only constructors and conversion operators may be declared explicit.
My questions
Why is explicit not allowed for assignment operators or other methods? IMO it would make a lot of sense to allow any method to be explicit as well.
Are there other solutions to prevent implicit type conversion for the assignment operator?
Your problem isn't in the class Result: you are explicitly creating a new instance of it, after all; explicit cannot forbid it.
I don't think you can forbid the implicit promotion bool -> long.
You can work around it. One way is to make ResultCode not be an integer type. then, it could have an explicit constructor. Something like
class ResultCode
{
unsigned long m_code;
public:
explicit ResultCode(unsigned long code) : m_code(code) {}
operator unsigned long () { return m_code; }
};
would allow you to use ResultCode anywhere you can use a unsigned int and create it as ResultCode res = 5 or return ResultCode(5) but not call a function expecting a ResultCode (such as the Result constructor!) with anything which is not a ResultCode already, nor do something like return 5 if the function must return a ReturnCode.
Otherwise you can use template overloadng to 'catch' anything not being an unsigned int and force it to be an error
typedef unsigned long ResultCode;
class Result
{
ResultCode m_par;
public:
template<typename T>
Result(T param) { static_assert(false); }
template<>
Result(ResultCode par): m_par(par) {}
};
int main()
{
ResultCode a = 5; //ok
//unsigned long a = 6; //also ok
//bool a = true; //error!
//int a = 7; //also error!!
Result b(a);
}
With the current implementation of Result's assignment operator, this is not going to give me a compiler error - the boolean return value of doSomething() is implicitly converted to unsigned long.
With respect to the code you posted; it does result in an error error: no viable conversion from 'bool' to 'Result', see here.
A minimal example showing the behaviour you see in the code would be required. There is likely other constructors or type with conversion in the actual code that have a material effect on your code.
On the explicitly asked questions...
Why is explicit not allowed for assignment operators or other methods?
explicit is only allowed where implicit conversion can take place, i.e. where the compiler would attempt to generate the conversion for you (there is a special case for bool). Such conversions are constructors and the conversion (or casting operators).
Marking the constructor or conversion operator as explicit prevents the compiler from making the conversions, hence, if you require the conversion, you need to be explicit about it - as a general motivation for why this is done, it makes the code more explicit in what it does. There is a trade-off, so judicious use should be applied in both cases. The general advice is to favour explicit when in doubt.
For example;
struct Result {
Result(long src); // can be marked explicit
operator long() const; // can be marked explicit
};
Are there other solutions to prevent implicit type conversion for the assignment operator?
The assignment operator has a particular for Result& operator=(Result&);. In the assignment itself, there are no conversion. To prevent the implicit creation of a Result for the assignment, the constructor(s) need to be marked explicit.
To prevent the Result from being created from a ResultCode, you can either not declare the method, or mark it as deleted;
Result& operator=(ResultCode code) = delete;
This question is easiest to illustrate with an example, so here goes:
Is code like the following guaranteed to be valid, and compile & run correctly?
(Not all implementations actually compile it correctly, but I'm wondering if that's a bug.)
#include <algorithm>
class Picky
{
friend
Picky *std::copy<Picky const *, Picky *>(Picky const *, Picky const *, Picky *);
Picky &operator =(Picky const &) { return *this; }
public:
Picky() { }
};
int main()
{
Picky const a;
Picky b;
std::copy<Picky const *, Picky *>(&a, &a + 1, &b);
return 0;
}
std::copy requires an output iterator ([algorithms.general]/p5); output iterators, among other things, require *r = o to be valid ([output.iterators], Table 108) - not just "valid sometimes" or "valid in some contexts".
Since for Picky *p, a;, *p = a isn't valid in most contexts, Picky * isn't a valid output iterator.
Hmm it'd be great if you could generalize your answer to other things
beyond the particular example I gave. Like, for example,
std::vector::push_back(T const &), or whatever.
Befriending a member function is an absolute no-no, because you aren't even guaranteed that there's a member function with that signature ([member.functions]/p2, which Stephan T. Lavavej calls the "STL Implementers Can Be Sneaky Rule"):
An implementation may declare additional non-virtual member function
signatures within a class:
by adding arguments with default values to a member function signature187 [Note: An implementation
may not add arguments with default values to virtual, global, or non-member functions. — end note];
by replacing a member function signature with default values by two or more member function signatures with equivalent behavior; and
by adding a member function signature for a member function name.
187 Hence, the address of a member function of a class in the C++ standard library has an unspecified type.
The code might compile if std::copy() does not call any other non-friend functions but I've yet to encounter any such implementation. And there is no requirement in the standard limiting HOW std::copy() achieves the required effect.
However, it does require a working and accessible assignment operator.
When I try to compile (with gcc 4.3.4) this code snippet:
enum SimpleEnum {
ONEVALUE
};
void myFunc(int a) {
}
void myFunc(char ch) {
}
struct MyClass {
operator int() const { return 0; };
operator SimpleEnum() const { return ONEVALUE; };
};
int main(int argc, char* argv[]) {
myFunc(MyClass());
}
I get this error:
test.cc: In function "int main(int, char**)":
test.cc:17: error: call of overloaded "myFunc(MyClass)" is ambiguous
test.cc:5: note: candidates are: void myFunc(int)
test.cc:8: note: void myFunc(char)
I think I (almost) understand what the problem is, i.e. (simplifying it a lot) even if I speak about "char" and "enum", they all are integers and then the overloading is ambiguous.
Anyway, the thing I don't really understand is that if I remove the second overloading of myFunc OR one of the conversion operators of MyClass, I have no compilation errors.
Since I'm going to change A LOT of old code because of this problem (I'm porting code from an old version of HP-UX aCC to g++ 4.3.4 under Linux), I would like to understand better the whole thing in order to choose the best way to modify the code.
Thank you in advance for any help.
The conversion from MyClass is ambiguous, as there is one conversion to int and one to the enum, which is itself implicitly convertible to int, and both are equally good conversions. You can just make the call explicit, though, by specifying which conversion you want:
myfunc(int(MyClass()));
Alternatively, you might like to rethink why you have a function that has separate overloads for int and char, perhaps that could be redesigned as well.
enums are types in C++, unlike C.
There are implicit conversions for both enum -> char and enum -> int. The compiler just doesn't know which one to choose.
EDIT: After trying with different tests:
When the definition for custom conversion MyClass -> int is removed, code compiles.
Here there is implicit conversion for enum to int and so it is the one favored by the compiler over the one to char. Test here.
When the definition for void myFunc(int) is removed compilation fails.
Compiler tries to convert from MyClass to char and finds that, not having a user defined conversion operator char(), both user defined int() and SimpleEnum() may be used. Test here.
When you add a char() conversion operator for MyClass compilation fails with the same error as if not.
Test here.
So the conclusion I come up with here is that in your originally posted code compiler has to decide which of the two overloaded versions of myFunc should be called.
Since both conversions are possible:
MyClass to int via user defined conversion operator.
MyClass to int via user defined conversion (MyClass to SimpleEnum) + implicit conversion (SimpleEnum to char)
compiler knows not which one to use.
I would have expected that the int overload is called. Tried a few compilers and got different results. If you are going to remove anything, remove the user conversion operator to int, since enums have a standard conversion to ints.