conversion operator overloading ambiguity, compilers differ - c++

I have seen other questions on SO regarding this, but none that explains it in full. What is the right ways for compilers to handle the two situations below? I have tried it with gcc 4.7.1 (with -std=c++0x), VS2010 and VS2012 an get different results on all:
Example 1:
struct BB
{
// generic cast
template<typename T>
operator T() const
{
return 0;
}
// string cast
operator std::string() const
{
return string("hello");
}
};
int main()
{
BB b;
string s = b;
}
Output:
gcc 4.7.1: Ok
VS2010: Ok
VS2012: Fail: "Cannot convert from BB to
string"
Example 2:
struct BB
{
// generic cast
template<typename T>
operator T() const
{
return 0;
}
// string cast
operator std::string() const
{
return string("hello");
}
};
int main()
{
BB b;
string s = (string)b;
Output:
gcc 4.7.1: Fail: call of overloaded string(BB&) is ambigious
VS2010: Ok
VS2012: Fail: "Cannot convert from BB to string"

Your second version with a C-style cast is ambiguous. The problem is that there are two ways that static_cast<std::string> can convert an instance of class BB to a string. The obvious path is by using your non-template std::string cast operator to convert directly to a string. There is a more devious path, and all but VS2010 have found it. This other path is to use your template cast operator to convert a BB to an allocator for a string and then construct an empty string using this allocator.
Template cast operators are potentially dangerous beasts. You have just given the compiler the tools to convert a BB to anything.
Your first version escapes this problem because std::basic_string(const _Alloc& __a) is an explicit constructor. Explicit constructors can be used by explicit conversions but not by implicit ones. It is this constructor plus the conversion to an allocator that creates the ambiguity, and this path cannot be used with an implicit conversion.
As to why VS1012 fails on the implied conversion, it could be a bug in VS2012, or it could be that C++11 creates even more avenues to get from a BB to a std::string. I am not a C++11 expert. I'm not even a novice.
One more item: your code would have failed even more miserably had you used
std::string s;
s = b;
The assignment operator in conjunction with the template conversion operator creates more ways to get from b to s. Should the system convert b to a std::string, to a char, or a char*?
Bottom line: You really should rethink your use of template conversion operators.

The reason is fails under some compilers is because they go to different lengths trying to figure out what you're doing. The culprit is the templated operator call.
As this SO question explains, templated operators have to be called explicitly (b.operator()<std::string> if you wanted to call template<typename T> operator();), but, there is no way to call your templated operator:
b.operator std::string()<std::string>; //invalid
b.operator std::string <std::string>(); //also invalid, string takes no template arguments
// a bunch more weird syntax constructions...
The issue comes from the fact that the name after operator depends on the template argument, so there is no way to specify it. gcc and VS2012 figured out what you were doing and noticed they can fit the conversion to either the template or the explicitly defined one => ambiguous call. VS2010 didn't do so and is calling one of them, you can find which one through debugging.
Template specialization could've helped in a case like this, however, trying to define
// string cast
template<>
operator std::string() const
{
return string("hello");
}
will fail, since the compiler can't tell the difference between that function and a regular one without the template<>. If there were some arguments in the prototype it would've worked, but operator typename does have any...
Long story short - avoid templated conversion operators.

Related

Supposedly ambiguous explicit conversion operator in MSVC, not in gcc or clang

Consider the following class that implements a user-conversion to std::string and const char*:
class String {
public:
String(std::string_view s) : str{s} {}
operator std::string() const {
return str;
}
operator const char*() const {
return str.c_str();
}
private:
std::string str;
};
int main()
{
static_assert(std::is_convertible_v<String, std::string>);
String s("Hello World");
auto x = static_cast<std::string>(s);
return 0;
}
MSVC tells me that static_casting to std::string is ambiguous, while clang and gcc do not: https://godbolt.org/z/7de5YvTno
<source>(20): error C2440: 'static_cast': cannot convert from 'String' to 'std::string'
<source>(20): note: No constructor could take the source type, or constructor overload resolution was ambiguous
Compiler returned: 2
Which compiler is right?
As a follow-up question: I can fix the ambiguity by marking the conversion operations explicit. But then std::is_convertible_v<String, std::string> returns false. Is there a type trait is_explicitly_convertible or is_static_castible or similar?
PS: I know the simplest and cleanest solution would be to have a non-copying conversion operator to const std::string&, but I still want to understand why MSVC rejects the code.
This is CWG2327.
As written, the direct-initialization specified for static_cast strictly considers constructors for std::string, and the String argument requires incomparable user-defined conversions to satisfy either the move constructor or the converting constructor from const char*. Note that, regardless of the set of conversion functions available, a constructor must be called, which is the missed copy elision mentioned in the issue.
The intent is that constructors and conversion functions are simultaneously considered for the actual initialization of x. This is a bit unusual in that member functions of std::string and String are in the same overload set, but it resolves the ambiguity because calling operator std::string is an exact match for the (implied object) argument, and it allows x to be the return value from that function in the ordinary C++17 sense.
MSVC is implementing the standard as written, while GCC and Clang are implementing (something like) the intended resolution.
(std::is_constructible is more or less the direct-initialization trait you also asked about.)

std::function as template parameter

I currently have a map<int, std::wstring>, but for flexibility, I want to be able to assign a lambda expression, returning std::wstring as value in the map.
So I created this template class:
template <typename T>
class ValueOrFunction
{
private:
std::function<T()> m_func;
public:
ValueOrFunction() : m_func(std::function<T()>()) {}
ValueOrFunction(std::function<T()> func) : m_func(func) {}
T operator()() { return m_func(); }
ValueOrFunction& operator= (const T& other)
{
m_func = [other]() -> T { return other; };
return *this;
}
};
and use it like:
typedef ValueOrFunction<std::wstring> ConfigurationValue;
std::map<int, ConfigurationValue> mymap;
mymap[123] = ConfigurationValue([]() -> std::wstring { return L"test"; });
mymap[124] = L"blablabla";
std::wcout << mymap[123]().c_str() << std::endl; // outputs "test"
std::wcout << mymap[124]().c_str() << std::endl; // outputs "blablabla"
Now, I don't want to use the constructor for wrapping the lambda, so I decided to add a second assignment operator, this time for the std::function:
ValueOrFunction& operator= (const std::function<T()>& other)
{
m_func = other;
return *this;
}
This is the point where the compiler starts complaining. The line mymap[124] = L"blablabla"; suddenly results in this error:
error C2593: 'operator = is ambiguous'
IntelliSense gives some more info:
more than one operator "=" matches these operands: function
"ValueOrFunction::operator=(const std::function &other) [with
T=std::wstring]" function "ValueOrFunction::operator=(const T
&other) [with T=std::wstring]" operand types are: ConfigurationValue =
const wchar_t
[10] c:\projects\beta\CppTest\CppTest\CppTest.cpp 37 13 CppTest
So, my question is, why isn't the compiler able to distinguish between std::function<T()> and T? And how can I fix this?
The basic problem is that std::function has a greedy implicit constructor that will attempt to convert anything, and only fail to compile in the body. So if you want to overload with it, either no conversion to the alternative can be allowed, of you need to disable stuff that can convert to the alternative from calling the std::function overload.
The easiest technique would be tag dispatching. Make an operator= that is greedy and set up for perfect forwarding, then manually dispatch to an assign method with a tag:
template<typename U>
void operator=(U&&u){
assign(std::forward<U>(u), std::is_convertible<U, std::wstring>());
}
void assign(std::wstring, std::true_type /*assign_to_string*/);
void assign(std::function<blah>, std::false_type /*assign_to_non_string*/);
basically we are doing manual overload resolution.
More advanced techniques: (probably not needed)
Another approach would be to limit the std::function = with SFINAE on the argument being invoked is valid, but that is messier.
If you have multiple different types competing with your std::function you have to sadly manually dispatch all of them. The way to fix that is to test if your type U is callable with nothing and the result convertible to T, then tag dispatch on that. Stick the non-std::function overloads in the alternative branch, and let usual more traditional overloading to occur for everything else.
There is a subtle difference in that a type convertible to both std::wstring and callable returning something convertible to T ends up being dispatched to different overloads than the original simple solution above, because the tests used are not actually mutually exclusive. For full manual emulation of C++ overloading (corrected for std::functions stupidity) you need to make that case ambiguous!
The last advanced thing to do would be to use auto and trailing return types to improve the ability of other code to detect if your = is valid. Personally, I would not do this before C++14 except under duress, unless I was writing some serious library code.
Both std::function and std::wstring have conversion operators that could take the literal wide string you are passing. In both cases the conversions are user defined and thus the conversion sequence takes the same precedence, causing the ambiguity. This is the root cause of the error.

Conversion from my class to int

I am using a library that is templated and that I do not wish to modify. Namely CImg.
This library has been mostly designed to work with templates of simple types: float, double, int etc.
At some point, this library does:
CImg<T>& fill(const T val) {
if (is_empty()) return *this;
if (val && sizeof(T)!=1) cimg_for(*this,ptrd,T) *ptrd = val;
else std::memset(_data,(int)val,size()*sizeof(T));
return *this;
}
Now I want to use this library with a more complex class as template parameter. My particular class is such that sizeof(T)!=1 and most of the time, the fill function will properly assign val to each element with the proper operator= of my class. However, when !val, I would like a conversion operator that allows my class to be cast to an int and to produce some values (for example, 0 would make the function above work).
Right now, my program does not compile as it says:
error C2440: 'type cast' : cannot convert from 'const MyClass' to 'int'
How can I create an operator that allows for (int)my_variable with my_variable of type MyClass to be legal, without modifying the function above ?
Something like this using user defined conversions
int type;
explicit operator int()
{
return type;
}
What you want in this case is probably int conversion operator overload:
class A{
public:
explicit operator int() const{
return 2;
}
};
EDIT:
|I added explicit conversion that should make your code compile (at least the method you showed us), and not mess-up other operators, but it's only allowed since C++11, so if you are using older compiler it might not be available yet.

c++ conversion operator overloading, enums, ints and chars

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.

Implicit casting in templates and compiler coercion

I have this very simple wrapper template:
template<class T>
struct wrapper {
inline operator T () {
return v;
}
inline wrapper(T v):v(v) { }
T v;
};
Trying to use it with any non-primitive type with (for example) comparison operator relying on the template having it defined doesn't look promising:
std::string t = "test";
assert(t == t);
typedef wrapper<std::string> string_wrapper;
assert(string_wrapper(t) == string_wrapper(t));
GCC 4.4.5 complains with this error:
error: no match for ‘operator==’ in ‘wrapper<std::basic_string<char> >(std::basic_string<char>(((const std::basic_string<char>&)((const std::basic_string<char>*)(& t))))) == wrapper<std::basic_string<char> >(std::basic_string<char>(((const std::basic_string<char>&)((const std::basic_string<char>*)(& t)))))’
What is interesting is that GCC triple-casts the template then fails to use operator == that was defined for std::string.
I don't think that implicit coercion is impossible, since if I change std::string to int or double, bool or anything primitive, GCC will choose the correct operator.
I do not want to define operator == for the wrapper struct, because that operator is just an example, and I need wrapper to 'feel' just like the real type regarding operators.
Just in cast GCC misunderstands my syntax, if I create a wrapper and try to compare it to itself, GCC complains again (though without triple casting) that it cannot find a matching == operator:
typedef wrapper<std::string> string_wrapper;
string_wrapper tw(t);
assert(tw == tw);
error: no match for ‘operator==’ in ‘tw == tw’
Why can't GCC find and/or use std::string operator == std::string when wrapper provides the cast?
That operator T is called a "conversion operator" or "conversion function" instead of a cast. "Conversions" are implicit; casting is explicit.
The compiler can't find the operator== for std::string because the overload resolution rules don't allow that to happen. More details about what you're really trying to do could help provide a solution.
Apparently this is a GCC 4.5 bug. The code is valid, unlike what Fred Nurk says.
http://gcc.gnu.org/bugzilla/show_bug.cgi?id=45383
The questioner has answered itself with some GCC PR, but which really does not answer his question.
The reason is like #Fred describes. Reducing it:
template<typename T>
struct A {
operator T() { return T(); }
};
int main() {
A<std::string>() == A<std::string>();
}
What can the compiler do? It could call operator std::string() on both operands and then do the comparison. But why should it do that call in the first place? It first needs to find an operator== that has two parameters of type std::string. Let's look at how its operator is defined
template<class charT, class traits, class Allocator>
bool operator==(const basic_string<charT,traits,Allocator>& lhs,
const basic_string<charT,traits,Allocator>& rhs);
There we have it. It first needs to do template argument deduction, and the fact that A does not match const basic_string<> will make it so that this operator== is ignored. You are lucky that operator== is found anyway using ADL so that it does argument deduction in the first place (since std::string is a template argument of your type, it will consider namespace std by ADL and find this operator).
So we have no suitable operator== to call and therefor GCC is alright with rejecting your code for the reasons #Fred gave in a nutshell. In the end, trying to make a class behave like another type is deemed to failure.