Reputation: 2980
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:
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:
Upvotes: 8
Views: 2768
Reputation: 33116
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.
Upvotes: 5
Reputation: 843
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.
Upvotes: 2