Here's a largely academic exercise in understanding conversion operators, templates and template specializations. The conversion operator template in the following code works for int
, float
, and double
, but fails when used with std::string
... sort of. I've created a specialization of the conversion to std::string
, which works when used with initialization std::string s = a;
, but fails when used with a cast static_cast<std::string>(a)
.
#include <iostream>
#include <string>
#include <sstream>
class MyClass {
int y;
public:
MyClass(int v) : y(v) {}
template <typename T>
operator T() { return y; };
};
template<>
MyClass::operator std::string() {
std::stringstream ss;
ss << y << " bottles of beer.";
return ss.str();
}
int main () {
MyClass a(99);
int i = a;
float f = a;
double d = a;
std::string s = a;
std::cerr << static_cast<int>(a) << std::endl;
std::cerr << static_cast<float>(a) << std::endl;
std::cerr << static_cast<double>(a) << std::endl;
std::cerr << static_cast<std::string>(a) << std::endl; // Compiler error
}
The above code generates a compiler error in g++ and icc, both complaining that no user-defined conversion is suitable for converting a MyClass
instance to a std::string
on the static_cast
(C-style casts behave the same).
If I replace the above code with explicit, non-template versions of the conversion operator, everything is happy:
class MyClass {
int y;
public:
MyClass(int v) : y(v) {}
operator double() {return y;}
operator float() {return y;}
operator int() {return y;}
operator std::string() {
std::stringstream ss;
ss << y << " bottles of beer.";
return ss.str();
}
};
What is wrong with my template specialization for std::string
? Why does it work for initialization but not casting?
Update:
After some template wizardry by @luc-danton (meta-programming tricks I'd never seen before), I have the following code working in g++ 4.4.5 after enabling experimental C++0x extensions. Aside from the horror of what is being done here, requiring experimental compiler options is reason enough alone to not do this. Regardless, this is hopefully as educational for others as it was for me:
class MyClass {
int y;
public:
MyClass(int v) : y(v) {}
operator std::string() { return "nobody"; }
template <
typename T
, typename Decayed = typename std::decay<T>::type
, typename NotUsed = typename std::enable_if<
!std::is_same<const char*, Decayed>::value &&
!std::is_same<std::allocator<char>, Decayed>::value &&
!std::is_same<std::initializer_list<char>, Decayed>::value
>::type
>
operator T() { return y; }
};
This apparently forces the compiler to choose the conversion operator std::string()
for std::string
, which gets past whatever ambiguity the compiler was encountering.