Martin's Tex-Blog

Posts on programming and generally technical topics

Platform dependend overload resolution ambiguity

leave a comment »


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

Written by Marcin

January 17, 2015 at 3:10 am

Posted in C++, Uncategorized

Friend definition – or friend functions defined inside class body

leave a comment »


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

Written by Marcin

January 11, 2015 at 1:08 am

Posted in C++

Implicit type conversions with templates

leave a comment »


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.

Written by Marcin

January 3, 2015 at 2:54 am

Posted in C++

Default template parameter redeclaration

leave a comment »


[temp.param]/12 Clearly states :

A template-parameter shall not be given default arguments by two different declarations in the same scope.

now, both clang and g++ generates errors for following code:

template<class T = int> class X;
template<class T = int> class X {};

msvc on the other hand (even with no extensions enabled) compiles it fine, I had to change second default parameter from int to some other type to see error. So its not that really bad, but still ‘shall not’ means ‘is not allowed’ as per ISO/IEC Directives. msvc on the other hand gives error in similar case but with redeclaration of function default parameter values.

Written by Marcin

December 31, 2014 at 12:17 am

Posted in C++

String literal as non type template parameter

leave a comment »


Unfortunately it is not possible to have template with string literal as a non type parameter. Until C++11 reason was that string literals had internal linkage, while non type template parameters allowed only external linkage pointers. It makes sense because templ_class should have the same typeid in all translation units. It kind of could work with string pooling enabled – which is availabe for both gcc and msvc. After C++11 the reason is more of that string literals does not match any of the allowed entities as template parameter.

You can use character array initialized with string literal as non type parameter. It compiles both with g++ and msvc:

#include <typeinfo>

template<const char* STR>
class MyStr {};

extern const char my_string[]; const char my_string[] = "alfabet";
extern const char my_string2[]; const char my_string2[] = "alfabet";

// or use constexpr
//constexpr const char my_string[] = "alfabet";
//constexpr const char my_string2[] = "alfabet";

extern const char* my_string_pc;
const char* my_string_pc = "alfabet";

int main(){
   
    // This fails with:
    //  main.cpp:30:31: error: 'my_string_pc' is not a valid template argument because 'my_string_pc' is a variable, not the address of a variable
    //    typedef MyStr<my_string_pc> str_type3;
    typedef MyStr<my_string_pc> str_type3;     
   
    // Below compiles, but gives different types
    typedef MyStr<my_string> str_type1;   
    typedef MyStr<my_string2> str_type2; 
   
    // This fails
    static_assert(std::is_same<str_type1, str_type2>::value, "");
   
    return 0;
}

EDIT:
After writing it I have found in standard ([temp.arg.nontype]) the actual reasoning for all above:

[ Note: A string literal (2.14.5) does not satisfy the requirements of any of these categories and thus is not
an acceptable template-argument. [ Example:

template<class T, const char* p> class X {
/ ... /
};
X<int, "Studebaker"> x1; // error: string literal as template-argument
const char p[] = "Vivisectionist";
X<int,p> x2; // OK

—end example ] —end note ]

refs:
http://stackoverflow.com/questions/5547852/string-literals-not-allowed-as-non-type-template-parameters

Written by Marcin

December 30, 2014 at 12:56 am

Posted in C++, Uncategorized

Linkage of templates

leave a comment »


I would like to share observation of another non standard behavior found in Visual Studio compiler. According to C++ Templates Complete Guide: “class templates cannot share a name with a different kind of entity”, below code:

int X;
template
class X {    
};

gives following error with g++ 4.9:


main.cpp:8:7: error: 'template struct X' redeclared as different kind of symbol
class X {
^
main.cpp:6:5: note: previous declaration 'int X'
int X;

but with VS2013 compiles just fine.

Written by Marcin

December 28, 2014 at 2:24 am

Posted in Uncategorized

Be carefull with subnormal floats

leave a comment »


Subnormal floating point numbers are values that are very small (.0f to ~.1f), operations on them are specially treated by CPUs. Below is a timing of simple sumation of floating point values, you can see that once subnormal values are used, performance instantly drops 10 or more times.

http://coliru.stacked-crooked.com/a/6ffe763ab4b5ada0

// Example code for testing subnormal float efficency
float f = 2.0f;
std::chrono::steady_clock::duration prevdiff;
for (int k = 0; k < 10000; ++k) {
  float sum = .0f;		
  auto start = std::chrono::steady_clock::now();

  for (int n = 0; n < 1e7; ++n) {
    sum += f * 0.1f;  // interesting, but simple summation show no performance degradation
  }

  auto end = std::chrono::steady_clock::now();
  auto diff = end - start;
  std::cout << k << ", f=" << f << ", isnormal=" << std::isnormal(f)
    << ", elapsed=" << std::chrono::duration_cast<std::chrono::milliseconds>(diff).count() << "ms" <<
    ", ratio=" << (prevdiff.count() == 0 ? 0 : (diff.count() / prevdiff.count())) << std::endl;
    prevdiff = diff;

   if (f < std::numeric_limits<float>::min())
     break;

    f /= 2.0f;
}

here is the interesting part from the output:

123, f=1.88079e-37, isnormal=1, elapsed=69ms, ratio=1
124, f=9.40395e-38, isnormal=1, elapsed=799ms, ratio=11 <-------- here performance drop
125, f=4.70198e-38, isnormal=1, elapsed=800ms, ratio=1
126, f=2.35099e-38, isnormal=1, elapsed=800ms, ratio=0
127, f=1.17549e-38, isnormal=1, elapsed=796ms, ratio=0
128, f=5.87747e-39, isnormal=0, elapsed=985ms, ratio=1

whats interesting is that this drop happens before std::isnormal returns true. You can use _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON); to make CPU not do computations using subnormal values.

This post is based on following SO : http://stackoverflow.com/questions/9314534/why-does-changing-0-1f-to-0-slow-down-performance-by-10x.

Written by Marcin

December 21, 2014 at 4:40 pm

Posted in Uncategorized

Follow

Get every new post delivered to your Inbox.