Archive for January 2015
Platform dependend overload resolution ambiguity
Here I will show case when types used internally by compiler might cause ambiguities in overload resolution. Below is example code:
#include <string> struct CoolString { std::string s; CoolString(const char* p) : s(p){} char& operator[](size_t i) { return s.at(i); } operator char*() { return &s[0]; } }; int main() { CoolString ss("asd"); ss[1] = '1'; // {1} }
It looks valid, althought use of implicit user conversion operator to char* is a sign of bad design (std::string provides c_str instead). Above code compiles under g++ and clang, but fails to compile under Visual Studio. The error is that call {1} is ambiguous, and looks like below:
2>c:\prj\test.cpp(10) : error C2666: 'CoolString::operator []' : 2 overloads have similar conversions 2> c:\prj\test.cpp(10): could be 'char &CoolString::operator [](size_t)' 2> or 'built-in C++ operator[(char *, int)' 2> while trying to match the argument list '(CoolString, int)'
so, compiler is unable to decide which one of those to choose:
ss.operator[](1) // line 1 ss.operator char*()[1] // line 2
In case of second choice ( line 2 ), compiler wants to apply built in subscript operator after user defined conversion to char*. Built in subscript operator takes index as a ptrdiff_t type. Because this is a typedef, lets check how it differs under g++/clang from VS:
std::cout << "size_t = " << typeid(size_t).name() << std::endl; // VS2013: unsigned int, g++: unsigned long std::cout << "ptrdiff_t = " << typeid(ptrdiff_t).name() << std::endl; // VS2013: int, g++: long
Under VS ptrdiff_t is int, while under g++/clang its long. While size_t is always unsigned. Lets look what conversions are required by both candidates:
ss.operator[](1) – here only integral promotion of int to unsigned int or unsigned long is required
ss.operator char*()[1] – here on VS only user defined conversion must be applied to ss (so that operator char*() is called), since 1 is an int it matches ptrdiff_t which under VS is also int. But under g++/clang two conversions are requried: first user defined to char*, and then promotion of int to long.
Now its clear why VS shows error, implicit conversions sequences are ambiguous – both are of same lenghts, also they differ on different positions, but I am not clear about it here (exact standard wording) – you could argue that integral promotion should be more promoted than user defined conversion.
Under g++/clang built in operator is not choosen because it requires two implicit conversions, first is user defined conversion, second is promotion int to long. So compiler choses ss.operator[](1) which requires only integral promotion.
The solution is to:
1. Dont use implicit conversion operator to char*
2. Replace size_t with int in char& operator[](size_t i), this will make it a perfect match and top priority candidate.
3. Use ss[1u]
For more references:
– http://stackoverflow.com/questions/1726740/c-error-operator-2-overloads-have-similar-conversions
– “C++ Templates – The Complete Guide” – B.2.1
Friend definition – or friend functions defined inside class body
This is a slightly less known feature of C++ which allows you to define a function that can be called only by using Argument Dependent Lookup (ADL). Compiler will create non-member function in the namespace surrounding class where it is defined. This is how it looks like:
namespace Forest { class CTree { public: CTree(int) {} // This parameter less foo function is actually unaccessible friend void foo() {} // This one is accessibly only through ADL friend void foo(const CTree&) {} }; } Forest::CTree tree(0); foo(tree); // ok - call by ADT //foo(); // error //foo(0); // error - foo is not considered as candidate using ADL due to required implicit conversion //Forest:foo(); // error //Forest::foo(tree); // error //Forest::foo(); // error
So, how is that usefull to us? It allows you to call a foo function without qualifying it with namespace name. It makes greater sense in case of operator definitions. If you define operator== as a friend inside class body, then it will get called only by ADL – as it should, and it will also have full access to your class private data. Coders mostly define binary operators as a free function to allow for implicit conversions of arguments. The problem with friend defined operator is that implicit conversion will not happen.
references:
http://stackoverflow.com/questions/8207633/whats-the-scope-of-inline-friend-functions
http://stackoverflow.com/questions/381164/friend-and-inline-method-whats-the-point
Implicit type conversions with templates
Suppose you want to write a template class X that must allow for operations like X = X + Y or X = Y + X, where Y is any type like int, float or other class type. How you want to start with that? Normally you would write operator+ as a free function and add it as a friend to X. Lets assume we want Y to be an int. Below code seems to solve this scenario:
template<typename T> class X { public: int n; X(int rn) : n(rn) {} template<class U> friend X<U> operator+(const X<U>& lhs, const X<U>& rhs); }; template<typename U> X<U> operator+(const X<U>& lhs, const X<U>& rhs) { X<U> ret(0); ret.n = lhs.n + rhs.n; return ret; } int main() { X<int> a = 1; X<int> b = 2; X<int> c = a + b; c = c + 1; c = 1 + c; std::cout << "X : " << c.n << std::endl; }
unfortunately this will result in errors (compilation using clang):
source_file.cpp:25:7: error: invalid operands to binary expression ('X<int>' and 'int') c = c + 1; ~ ^ ~ source_file.cpp:15:7: note: candidate template ignored: could not match 'X<type-parameter-0-0>' against 'int' X<U>& operator+(const X<U>& lhs, const X<U>& rhs) { ^ ...
the problem is that template operator+ is not considered as a valid overload. In case of function templates, compiler will perform type deduction on the arguments and come up with a template parameter types that will match the call or else it will remove such function from list of potential candidates. Whats important here is that no implicit conversions will be done. So to make above code compile we would have to change class usage to:
c = c + X<int>(1); c = X<int>(1) + c;
this way, there is no need for implicit conversion using X(int rn) constructor. But this is not what we want. The solution is to use a language feature that enables you to define a non-member friend function inside template class definition, as in the following example:
template<typename T> class X { public: int n; X(int rn) : n(rn) {} friend X operator+(const X& lhs, const X& rhs) { X ret(0); ret.n = lhs.n + rhs.n; return ret; } }; int main() { X<int> a = 1; X<int> b = 2; X<int> c = a + b; c = c + 1; c = 1 + c; std::cout << "X : " << c.n << std::endl; }
Now this compiles and outputs X: 5. Lets analyse what friend X operator+(const X& lhs, const X& rhs)
means. First, X is the same as X<T>, due to class name injection (within the scope of a class template X with template parameters P1, P2, … the name of that template (X) can be equivalent to the template-id X<P1, P2, …>). Second thing is that compiler will generate for any used template parameter type a free non-template function at namespace level (outside of class X) which will be used during normal function call. Whats important for us here is that compiler will perform implicit conversions on its arguments. Such functions will be generated always, even if not used by given class instantiation. Below is example of such compiler generated free function:
X<int> operator+(const X<int>& lhs, const X<int>& rhs) { X<int> ret(0); ret.n = lhs.n + rhs.n; return ret; }
This is not a new feature in C++, I suppose its c++98 wording is easier to understand.
C++98 [temp.friend]p5: When a function is defined in a friend function declaration in a class template, the function is defined at each instantiation of the class template. The function is defined even if it is never used. C++11 [temp.friend]p4: When a function is defined in a friend function declaration in a class template, the function is instantiated when the function is odr-used. The same restrictions on multiple declarations and definitions that apply to non-template function declarations and definitions also apply to these implicit definitions.