Priority and ambiguity of explicit conversion operator templates - c++

I've been playing around with templated explicit conversion operators in my project, to implement explicit conversion from custom variant-like type. The minimal example reproducing my problem looks like the following (in C++14 mode):
#include <iostream>
#include <stdexcept>
#include <cmath>
using namespace std;
class A
{
public:
template<typename T> explicit operator T() const // 1
{
cout << "operator T" << endl;
return T();
}
template<typename T> explicit operator const T&() const // 2
{
cout << "operator const T&" << endl;
throw runtime_error("operator const T&");
}
template<typename T> explicit operator T&() // 3
{
cout << "operator T&" << endl;
throw runtime_error("operator T&");
}
};
int main(int, char**)
{
try
{
const A& a = A();
cout << abs(static_cast<double>(a) - 3.14) << endl;
}
catch (const runtime_error&)
{
}
return 0;
}
The problem I faced is the operator chosen for the static_cast conversion. With the GCC it's sort of expected (1) case. The output is:
operator T
3.14
But Clang refuses to compile this with the following output:
main.cpp:37:20: error: ambiguous conversion for static_cast from 'const A' to 'double'
cout << std::abs(static_cast<double>(a) - 3.14) << endl;
^~~~~~~~~~~~~~~~~~~~~~
main.cpp:10:32: note: candidate function [with T = double]
template<typename T> explicit operator T() const
^
main.cpp:16:32: note: candidate function [with T = double]
template<typename T> explicit operator const T&() const
^
1 error generated.
Why Clang considers conversion (2), when it apparently would require additional constructor call in the conversion sequence, while (1) wouldn't? And is it right (and GCC is then wrong) doing so?

static_cast performs either implicit conversion or direct initialisation. In your case, implicit conversion is not viable, but direct initialisation is because static_cast considers explicit constructors and conversion functions. So my guess is that clang (in my opinion correctly) identifies that there are two possible direct intialisations and complains accordingly. Not sure what is going on inside GCC, maybe it defaults to operator T() if it can find one, regardless of whether other ones exist.

Related

Use of overloaded operator '[]' is ambiguous with template cast operator

The following code compiles well in gcc 7.3.0, but doesn't compiles with clang 6.0.0.
#include <string>
struct X {
X() : x(10) {}
int operator[](std::string str) { return x + str[0]; }
template <typename T> operator T() { return x; } // (1) fails only in clang
//operator int() { return x; } // (2) fails both in gcc and clang
private:
int x;
};
int main() {
X x;
int y = 20;
int z = int(x);
return x["abc"];
}
I used command clang++ 1.cpp -std=c++98 with specifying different standard versions. I tried c++98,11,14,17,2a. In all cases an error is the same. Error message in clang is following:
1.cpp:14:13: error: use of overloaded operator '[]' is ambiguous (with operand types 'X' and 'const char [4]')
return x["abc"];
~^~~~~~
1.cpp:5:9: note: candidate function
int operator[](std::string str) { return x + str[0]; }
^
1.cpp:14:13: note: built-in candidate operator[](long, const char *)
return x["abc"];
^
1.cpp:14:13: note: built-in candidate operator[](long, const volatile char *)
1 error generated.
What compiler correctly follows the standard in this situation? Is it a valid code?
The description of the problem can be found here, but it is about situation (2). I am interested in case (1).
GCC is wrong. The template case shouldn't make any difference.
[over.match.best]/1 says:
Define ICSi(F) as follows:
...
let ICSi(F) denote the implicit conversion sequence that converts the i-th argument in the list to the type of the i-th parameter of viable function F. [over.best.ics] defines the implicit conversion sequences and [over.ics.rank] defines what it means for one implicit conversion sequence to be a better conversion sequence or worse conversion sequence than another.
Given these definitions, a viable function F1 is defined to be a better function than another viable function F2 if for all arguments i, ICSi(F1) is not a worse conversion sequence than ICSi(F2), and ...
The two viable candidates are
int operator[](X&, std::string); // F1
const char& operator[](std::ptrdiff_t, const char*); // F2
... and ICS1(F1) (X -> X&) is better than ICS1(F2) (X -> std::ptrdiff_t), no matter whether or not X -> std::ptrdiff_t is through a template conversion function, but ICS2(F1) (const char[4] -> std::string) is worse than ICS2(F2) (const char[4] -> const char*). So neither function is better than the other, resulting in ambiguity.
This has been reported as a GCC bug.
The issue is that there is one conversion on each path:
first from "abc" to std::string and then operator[] call.
second from x to std::ptrdiff_t and then the operator[] for an std::ptrdiff_t and a const char*.
So the solution is to make the conversion operator explicit:
int operator[](const std::string& str) { return x + str[0]; }
template <typename T>
explicit operator T() { return x; } // (1) fails only in clang

User-defined conversion operator template and built-in operators: no match for operator

Consider the following MCVE.
#include <type_traits>
struct A {
template<typename T, typename std::enable_if<std::is_same<T,int>::value,int>::type = 0>
operator T() const { return static_cast<T>(1); }
};
int main() {
int x = 1;
A a;
return x + a;
}
clang compiles it fine. DEMO
But GCC fails with:
error: no match for 'operator+' (operand types are 'int' and 'A')
return x + a;
~~^~~
Question: who is right and why?
I believe clang is right.
To do lookup on +, since at least one argument has class type, we consider member, non-member, and builtin candidates. There aren't any member or non-member candidates, so that's eay enough. There is a builtin candidate for int operator+(int, int), which is the only candidate. That candidate is viable because A can be convertible to int, directly (we have a standard conversion from A to const A& for the implicit object parameter, and then the user defined conversion from that to int, there's no further conversion necessary). As we have one viable candidate, that trivially makes it the best viable candidate.
Note that if A just had operator int() const { return 1; }, gcc would accept it. It's just the conversion function template that fails to be considered.

Overloaded operator ambiguity on Clang but not on GCC, which one is correct?

#include <iostream>
template <typename T>
struct Wrapper {
operator T const &() const & {
std::cout << "Wrapper::operator T const &() const &\n";
return _obj;
}
operator T&() & {
std::cout << "Wrapper::operator T&() &\n";
return _obj;
}
operator T&&() && {
std::cout << "Wrapper::operator T&&() &&\n";
return std::move(_obj);
}
private:
T _obj;
};
struct Test {
Test& operator=(Test const &test) {
std::cout << "Test& Test::operator=(Test const &)\n";
return *this;
}
Test& operator=(Test &&test) {
std::cout << "Test& Test::operator=(Test &&)\n";
return *this;
}
};
int main() {
Test test;
Wrapper<Test> wrapperTest;
test = wrapperTest; // OK for all
test = std::move(wrapperTest); // OK for GCC and ICC, not for Clang and VC++
return 0;
}
VC++ :
(34): error C2593: 'operator =' is ambiguous
(26): note: could be 'Test &Test::operator =(Test &&)'
(25): note: or 'Test &Test::operator =(const Test &)'
(69): note: while trying to match the argument list '(Test, Wrapper)'
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
Clang :
:34:7: error: use of overloaded operator '=' is ambiguous (with operand types 'Test' and 'typename std::remove_reference &>::type' (aka 'Wrapper'))
test = std::move(wrapperTest); // OK for GCC and ICC, not for Clang and Microsoft Visual C++
~~~ ^ ~~~~~~~~~~~~~~~~~~~~~~
:25:8: note: candidate function
Test& operator=(Test const &test) { std::cout << "Test& Test::operator=(Test const &)\n"; return *this; }
^
:26:8: note: candidate function
Test& operator=(Test &&test) { std::cout << "Test& Test::operator=(Test &&)\n"; return *this; }
^
1 error generated.
I think gcc and icc are correct.
test = std::move(wrapperTest);
assigns a Wrapper<Test>&& to a Test, since no candidate matches for this call, it will consider to operation which takes at most 1 conversion.
From Value categories:
When used as a function argument and when two overloads of the function are available, one taking rvalue reference parameter and the other taking lvalue reference to const parameter, an rvalue binds to the rvalue reference overload (thus, if both copy and move constructors are available, an rvalue argument invokes the move constructor, and likewise with copy and move assignment operators).
And Non-static member functions:
A non-static member function can be declared with either an lvalue ref-qualifier (the token & after the function name) or rvalue ref-qualifier (the token && after the function name). During overload resolution, non-static cv-qualified member function of class X is treated as a function that takes an implicit parameter of type lvalue reference to cv-qualified X if it has no ref-qualifiers or if it has the lvalue ref-qualifier. Otherwise (if it has rvalue ref-qualifier), it is treated as a function taking an implicit parameter of type rvalue reference to cv-qualified X.
Now, we have these candidates:
Test& operator=(Test &&test) by operator T&&() &&
Test& operator=(Test const &test) by operator T const &() const &
Based on those paragraphs, compilers should choose Test& operator=(Test &&test) by operator T&&() &&

Why does outputting a class with a conversion operator not work for std::string?

This works, printing 1:
#include <iostream>
struct Int {
int i;
operator int() const noexcept {return i;}
};
int main() {
Int i;
i.i = 1;
std::cout << i;
}
However, this fails to compile on GCC 4.8.1:
#include <iostream>
#include <string>
struct String {
std::string s;
operator std::string() const {return s;}
};
int main() {
String s;
s.s = "hi";
std::cout << s;
}
Here are the relevant parts of the error:
error: no match for ‘operator<<’ (operand types are ‘std::ostream {aka std::basic_ostream}’ and ‘String’)
std::cout << s;
snip
template std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&, const std::basic_string<_CharT, _Traits, _Alloc>&)
operator<<(basic_ostream<_CharT, _Traits>& __os,
/usr/include/c++/4.8/bits/basic_string.h:2753:5: note: template argument deduction/substitution failed:
main.cpp:25:18: note: ‘String’ is not derived from ‘const std::basic_string<_CharT, _Traits, _Alloc>’
std::cout << s;
I only use std::cout and std::string, which have the same template arguments. I'm really not sure why this wouldn't be able to pick up the implicit conversion like it did for Int. Why does it work with int, but not std::string?
That operator is a free template function. User defined conversions do not get checked when matching against a template function arguments, it instead uses type pattern matching (substitution).
In theory a SFINAE overload using std::is_convertable<> would be able to do what you want, but that technique was not used when operator<< that outputs a std::string to a basic_ostream<char> was defined.
A manual overload to output your class to basic_ostream<...> will fix your problem.
I would do this:
struct String {
std::string s;
operator std::string() const {return s;}
friend std::ostream& operator<<( std::ostream& os, String const& self) {
return os<<self.s;
}
};
which has the added benefit of not creating a wasted copy.
The << operator seems to have a pool of overloads with types other than std::string.
as I have seen by using the clang++ compiler.
The compiler does the implicit conversion from String to std::string but it does not match any of the defined << operators.
If you define the << operator for std::string it will work
#include <iostream>
#include <string>
std::ostream& operator<<(std::ostream& s, const std::string& str)
{
s << str.c_str();
return s;
}
struct String {
std::string s;
operator std::string() const {return s;}
};
int main() {
String s;
s.s = "hi";
std::cout << s;
}
You can find more details on the same issue here: http://forums.codeguru.com/showthread.php?432227-RESOLVED-Implicit-conversion-to-std-string
As seen in one post;
The problem is the operator<< here is a template and no template instantiations can be made for the type TestClass since the user defined conversions are probably not being considered in argument deduction for templates for implicit instantiations (atleast I could not find in section 14.7.1 (Implicit instantiation). This results in an empty overload set for the call "std::cout << obj << '\n';" and hence the error. It does not matter if an instantiation already happened or not. Template candidates are chosen into overload set on exact matches (except for array to pointer decay and const qualification - http://groups.google.co.in/group/com...29910b6?hl=en&).
When you provide an explicit overload operator<< with type std::string, it is non-template and adds up in the overload set and hence invoking the implicit conversion while doing overload resolution/a callable match.

"ambiguous overload for 'operator[]'" if conversion operator to int exist

I'm trying to implement the vector like and the map like [] operator for a class. But I get error messages from my compilers (g++ and clang++). Found out that they only occurs if the class has also conversion operators to integer types.
Now I have two problems. The first is that I don't know why the compiler can't distinguish between [](const std::string&) and [](size_t) when the class has conversion operators to ints.
The second... I need the conversion and the index operator. How to fix that?
works:
#include <stdint.h>
#include <string>
struct Foo
{
Foo& operator[](const std::string &foo) {}
Foo& operator[](size_t index) {}
};
int main()
{
Foo f;
f["foo"];
f[2];
}
does not work:
#include <stdint.h>
#include <string>
struct Foo
{
operator uint32_t() {}
Foo& operator[](const std::string &foo) {}
Foo& operator[](size_t index) {}
};
int main()
{
Foo f;
f["foo"];
f[2];
}
compiler error:
main.cpp: In function 'int main()':
main.cpp:14:9: error: ambiguous overload for 'operator[]' in 'f["foo"]'
main.cpp:14:9: note: candidates are:
main.cpp:14:9: note: operator[](long int, const char*) <built-in>
main.cpp:7:7: note: Foo& Foo::operator[](const string&)
main.cpp:8:7: note: Foo& Foo::operator[](size_t) <near match>
main.cpp:8:7: note: no known conversion for argument 1 from 'const char [4]' to 'size_t {aka long unsigned int}'
The problem is that your class has a conversion operator to uint32_t, so the compiler does not know whether to:
Construct a std::string from the string literal and invoke your overload accepting an std::string;
Convert your Foo object into an uint32_t and use it as an index into the string literal.
While option 2 may sound confusing, consider that the following expression is legal in C++:
1["foo"];
This is because of how the built-in subscript operator is defined. Per Paragraph 8.3.4/6 of the C++11 Standard:
Except where it has been declared for a class (13.5.5), the subscript operator [] is interpreted in such
a way that E1[E2] is identical to *((E1)+(E2)). Because of the conversion rules that apply to +, if E1 is an
array and E2 an integer, then E1[E2] refers to the E2-th member of E1. Therefore, despite its asymmetric
appearance, subscripting is a commutative operation.
Therefore, the above expression 1["foo"] is equivalent to "foo"[1], which evaluates to o. To resolve the ambiguity, you can either make the conversion operator explicit (in C++11):
struct Foo
{
explicit operator uint32_t() { /* ... */ }
// ^^^^^^^^
};
Or you can leave that conversion operator as it is, and construct the std::string object explicitly:
f[std::string("foo")];
// ^^^^^^^^^^^^ ^
Alternatively, you can add a further overload of the subscript operator that accepts a const char*, which would be a better match than any of the above (since it requires no user-defined conversion):
struct Foo
{
operator uint32_t() { /* ... */ }
Foo& operator[](const std::string &foo) { /* ... */ }
Foo& operator[](size_t index) { /* ... */ }
Foo& operator[](const char* foo) { /* ... */ }
// ^^^^^^^^^^^
};
Also notice, that your functions have a non-void return type, but currently miss a return statement. This injects Undefined Behavior in your program.
The problem is that f["foo"] can be resolved as:
Convert "foo" to std::string (be it s) and do f[s] calling Foo::operator[](const std::string&).
Convert f to integer calling Foo::operator int() (be it i) and do i["foo"] using the well known fact that built-in [] operator is commutative.
Both have one custom type conversion, hence the ambiguity.
The easy solution is to add yet another overload:
Foo& operator[](const char *foo) {}
Now, calling f["foo"] will call the new overload without needing any custom type conversion, so the ambiguity is broken.
NOTE: The conversion from type char[4] (type type of "foo") into char* is considered trivial and doesn't count.
As noted in other answers, your problem is that [] commutes by default -- a[b] is the same as b[a] for char const*, and with your class being convertible to uint32_t this is as good a match as the char* being converted to std::string.
What I'm providing here is a way to make an "extremely attractive overload" for when you are having exactly this kind of problem, where an overload doesn't get called despite your belief that it should.
So here is a Foo with an "extremely attractive overload" for std::string:
struct Foo
{
operator uint32_t() {return 1;}
Foo& lookup_by_string(const std::string &foo) { return *this; }
Foo& operator[](size_t index) {return *this;}
template<
typename String,
typename=typename std::enable_if<
std::is_convertible< String, std::string >::value
>::type
> Foo& operator[]( String&& str ) {
return lookup_by_string( std::forward<String>(str) );
}
};
where we create a free standing "lookup by string" function, then write a template that captures any type that can be converted into a std::string.
Because it "hides" the user-defined conversion within the body of the template operator[], when checking for matching no user defined conversion occurs, so this is preferred to other operations that require user defined conversions (like uint32_t[char*]). In effect, this is a "more attractive" overload than any overload that doesn't match the arguments exactly.
This can lead to problems, if you have another overload that takes a const Bar&, and Bar has a conversion to std::string, the above overload may surprise you and capture the passed in Bar -- both rvalues and non-const variables match the above [] signature better than [const Bar&]!