I have this little piece of code:
template<typename T>
class Test
{
public:
//operator T() const { return toto; }
T toto{ nullptr };
};
void function(int* a) {}
int main(int argc, char** argv)
{
Test<int*> a;
function(a);
return 0;
}
It doesn't compile unless the line operator T() const { return toto; } is un-commented. This magically works, but I am not sure why (if I un-comment the line).
I do understand that if the line is commented, the type of a when passed to function() is incompatible with the expected type int*. So, of course, the compiler complains ... no problem.
I also understand that the operator returns the actual type of the object, therefore in this particular case the compiler is happy.
I don't understand why the operator is called in this particular case.
Is doing function(a) the same thing as doing function(a()), only the () are implicit?
operator T() const { return toto; } is a user defined conversion operator, it is not operator(). It's used to define that your class is convertible to a different type.
operator() would look like this instead:
void operator()() const { ... }
In your case, you are using int* as T. If you substitute it yourself in the operator, you will see that it becomes operator int*() const { return toto; } which means "my class can be converted to an int* and the result of that conversion is evaluated as return toto;".
The function function() only accepts an int* as its argument. When you provide a Test instance, the call is only legal if there is a way to convert from Test to int*, which is why the operator T is required for the code to compile.
Related
Consider the following class which contains a conversion function for the std::string type:
class SomeType
{
public:
SomeType(char *value)
{
_str = value;
}
operator std::string()
{
return std::string(_str);
}
private:
char *_str;
};
The following snippet fails to compile with the error: no operator "==" matches these operands
int main(int argc, char* argv[])
{
SomeType a("test");
if (a == std::string("test")) // ERROR on this line
{
int debug = 1;
}
return 0;
}
I realize I could define an operator== method that accepts std::string operand, but why doesn't the conversion function work?
The problem is that std::string is in fact a template and as such I imagine that it's comparison operator is also a template. And in that case the standard as I recall states that no implicit conversion will happen for the required arguments, which means you'd have to cast SomeType to string yourself for it to be called.
As stated in Item 46 of Effective C++:
[...], because implicit type conversions are never considered during template argument deduction. Never. Such conversions are used during function calls, yes, but before you can call a function, you have to know which functions exist. [...]
You can find more info here.
std::string is actually typedef to std::basic_string<char, i_do_not_remember>
There no operator == that get just std::string It's template one
template<...>
bool operator (basic_string<...>& a, basic_string<...>& b) {
//
}
But template types can't be deduced here. You may cast it manually:
if (static_cast<std::string>(a) == std::string("test"))
Creating a wrapper function for a constructor such as the following compiles just fine:
#include <iostream>
template <typename T>
class wrapper
{
public:
template <typename A0>
T* operator () (const A0& a0) const
{
return new T(a0);
}
};
class Foo
{
public:
Foo(int i) { std::cout << "Foo:Foo(" << i << ")" << std::endl; }
};
int main(int argc, char** argv)
{
wrapper<Foo>()(42);
return 0;
}
But the code does not compile when I update the line:
T* operator () (const A0& a0) const
to:
T* operator () (A0& a0) const
My guess is this has to do with the rvalue '42' not being bindable to to a non-const reference. But when I make the reference const this will mean that I could never call a constructor that actually takes a non-const reference. Can someone explain what is going on here, and what is the right thing to do to make it work?
My guess is this has to do with the rvalue '42' not being bindable to to a non-const reference.
Yes. Correct.
But when I make the reference const this will mean that I could never call a constructor that actually takes a non-const reference.
No. Incorrect. You can still call it with non-const reference. In fact, that is how const-ness works : non-const reference/pointer/object can implicitly convert into const reference/pointer/object, but vice-versa is not true.
So try it out.
I wanted to somehow extend the Microsoft type _variant_t so it accepts implicit/explicit conversions to/from additional types. To do so, I wrote the following class:
class value_type
{
public:
/* Constructors */
value_type(const std::string& str) : m_variant(str.c_str()) {}
template <typename Type> value_type(const Type& value) : m_variant(value) {}
/* Conversion operators */
operator const _variant_t&() const { return m_variant; }
operator std::string() const { return static_cast<const char*>(m_variant); }
template <typename Type> operator Type() const { return static_cast<Type>(m_variant); }
private:
_variant_t m_variant;
};
That is, if every instance of _variant_t in the code is replaced with value_type, it "works" the same.
Lets consider the following function, which returns a _variant_t:
_variant_t foo();
If I write:
std::string bar()
{
value_type v = foo();
return v;
}
It compiles just fine.
But if I change the previous code like that:
std::string bar()
{
return value_type(foo());
}
or:
std::string bar()
{
return static_cast<std::string>(value_type(foo()));
}
The compilation fails with the following message:
configuration.cpp(41) : error C2668: 'std::basic_string<_Elem,_Traits,_Ax>::basic_string' : ambiguous call to overloaded function
If I remove the template <typename Type> operator Type... line, everything compiles.
Now I understand what the compilater says (it doesn't know which operator to use) but I don't understand why: It would seem logical to use the operator std::string when converting to std::string. What am I missing ?
Thank you.
The problem is that string's constructor is overloaded. So if returning involves invoking a string constructor, then there are multiple choices: argument could be converted to const char*, allocator<char> or string.
static_cast<string>(x) is the same as string(x).
I suppose your first faulty example should read return string(foo());, not return value_type(foo());
I define two versions of overloaded operator[] function in a class array. ptr is a pointer to first element of the array object.
int& array::operator[] (int sub) {
return ptr[sub];
}
and
int array::operator[] (int sub) const {
return ptr[sub];
}
Now, if I define a const object integer1 the second function can only be called..... but if I make a non-const object and then invoke as below:
cout << "3rd value is" << integer1[2];
which function is called here?
In your second example, the non-const version will be called, because no conversion is required, and a call that requires no conversion is a better match than one that requires a conversion.
Ultimately, however, you have a basic problem here: what you really want is behavior that changes depending on whether you're using your object as an rvalue or an lvalue, and const doesn't really do that. To make it work correctly, you normally want to return a proxy object, and overload operator= and operator T for the proxy object:
template <class T>
class myarray {
T *ptr;
class proxy {
T &val;
proxy &operator=(proxy const &p); // assignment not allowed.
public:
proxy(T &t) : val(t) {}
operator T() const { return val; }
proxy &operator=(T const&t) { val = t; return *this; }
};
proxy const operator[](int sub) const { return proxy(ptr[sub]); }
proxy operator[](int sub) { return proxy(ptr[sub]); }
// obviously other stuff like ctors needed.
};
Now we get sane behavior -- when/if our array<int> (or whatever type) is const, our operator[] const will be used, and it'll give a const proxy. Since its assignment operators are not const, attempting to use them will fail (won't compile).
OTOH, if the original array<int> was not const, we'll get a non-const proxy, in which case we can use both operator T and operator=, and be able to both read and write the value in the array<int>.
Your const version should return const int& not int, so that the semantics are just the same between the two functions.
Once you've done that, it doesn't matter which one is used. If the const version has to be used because your object has a const context, then it will be... and it won't matter as you're not trying to modify anything. Otherwise, it'll use the non-const version... but with just the same effect.
When writing a class to act as a wrapper around a heap-allocated object, I encountered a problem with implicit type conversion that can be reduced to this simple example.
In the code below the wrapper class manages a heap-allocated object and implicitly converts to a reference to that object. This allows the wrapper object to be passed as the argument to the function write(...) since implicit conversion takes place.
The compiler fails, however, when trying to resolve the call to operator<<(...), unless an explicit cast is made (checked with MSVC8.0, Intel 9.1 and gcc 4.2.1 compilers).
So, (1) why does the implicit conversion fail in this case? (2) could it be related to argument-dependent lookup? and (3) is there anything that can be done to make this work without the explicit cast?
#include <fstream>
template <typename T>
class wrapper
{
T* t;
public:
explicit wrapper(T * const p) : t(p) { }
~wrapper() { delete t; }
operator T & () const { return *t; }
};
void write(std::ostream& os)
{
os << "(1) Hello, world!\n";
}
int main()
{
wrapper<std::ostream> file(new std::ofstream("test.txt"));
write(file);
static_cast<std::ostream&>( file ) << "(2) Hello, world!\n";
// file << "(3) This line doesn't compile!\n";
}
It fails because you're trying to resolve an operator of your wrapper<T> class that doesn't exist. If you want it to work without the cast, you could put together something like this:
template<typename X> wrapper<T> &operator <<(X ¶m) const {
return t << param;
}
Unfortunately I don't know of a way to resolve the return type at compile time. Fortunately in most cases it's the same type as the object, including in this case with ostream.
EDIT: Modified code by suggestion from dash-tom-bang. Changed return type to wrapper<T> &.
The compiler doesn't have enough context to determine that operator& will make a valid conversion. So, yes, I think this is related to argument-dependent lookup: The compiler is looking for an operator<< that can accept a non-const wrapper<std::ostream> as its first parameter.
I think the problem has to do with maintaining some compile-time constraints. In your example, the compiler first would have to find all the possible operator<<. Then, for each of them, it should try if your object could be automatically converted (directly or indirectly) to any of the types that each operator<< are able to accept.
This test can be really complex, and I think this is restricted to provide a reasonable compiling time.
After some testing, an even simpler example identifies the source of the problem. The compiler cannot deduce the template argument T in f2(const bar<T>&) below from implicit conversion of wrapper<bar<int> > to bar<int>&.
template <typename T>
class wrapper
{
T* t;
public:
explicit wrapper(T * const p) : t(p) { }
~wrapper() { delete t; }
operator T & () const { return *t; }
};
class foo { };
template <typename T> class bar { };
void f1(const foo& s) { }
template <typename T> void f2(const bar<T>& s) { }
void f3(const bar<int>& s) { }
int main()
{
wrapper<foo> s1(new foo());
f1(s1);
wrapper<bar<int> > s2(new bar<int>());
//f2(s2); // FAILS
f2<int>(s2); // OK
f3(s2);
}
In the original example, std::ostream is actually a typedef for the templated class std::basic_ostream<..>, and the same situation applies when calling the templated function operator<<.
Check the signature of the insertion operator... I think they take non-const ostream reference?
Confirmed with C++03 standard, signature of the char* output operator is:
template<class charT, class traits>
basic_ostream<charT,traits>& operator<<(basic_ostream<charT,traits>&, const charT*);
which does indeed take a non-const reference. So your conversion operator does not match.
As noted in the comment: this is irrelevant.
There are various limits in the Standard about conversions applied... maybe this would need to implicit conversions (your operator, and cast to base type) when at most one should be applied.