0

What exactly are the rules for C++ converting an operator=() assignment to a construction? Such as Foo foo = bar will actually call Foo's constructor accepting bar as an argument, if it exists. I've googled for how this works but can't seem to find anything.

I'm having a problem figuring out why the assignment below is trying to take a constructor but not taking the obviously correct one: HandlePtr( TYPE& resource ). Construction using actual construction syntax works fine, but not with assignment operator.

code (obviously edited for brevity):

template< typename TYPE >
class HandlePtr {
public:
    HandlePtr( void ) = default;
    HandlePtr( HandlePtr< TYPE >& other ) = default;
    HandlePtr( TYPE& resource ) {} // generally I would make this explicit, but for testing purposes I took it out
    ~HandlePtr( void ) = default;

public:
    HandlePtr<TYPE>& operator=( TYPE& resource ) { return *this; }
    HandlePtr<TYPE>& operator=( HandlePtr<TYPE>& other ) { return *this; }
};

int main ( void ) {
    int x = 5;
    HandlePtr< int > g( x ); // works
    HandlePtr< int > i;i = x; // works
    HandlePtr< int > h = x; // doesn't work

            // also tried this just out of curiosity:
    HandlePtr< int > h = HandlePtr< int >( x ); // also does not work

    return 0;
}

errors:

shit.cpp: In function ‘int main()’:
try.cpp:19:24: error: no matching function for call to ‘HandlePtr<int>::HandlePtr(HandlePtr<int>)’
   HandlePtr< int > h = x; // doesn't work
                        ^
try.cpp:19:24: note: candidates are:
try.cpp:7:3: note: HandlePtr<TYPE>::HandlePtr(TYPE&) [with TYPE = int]
   HandlePtr( TYPE& resource ) {} // generally I would make this explicit, but for testing purposes I took it out
   ^
try.cpp:7:3: note:   no known conversion for argument 1 from ‘HandlePtr<int>’ to ‘int&’
try.cpp:6:3: note: HandlePtr<TYPE>::HandlePtr(HandlePtr<TYPE>&) [with TYPE = int]
   HandlePtr( HandlePtr< TYPE >& other ) = default;
   ^
try.cpp:6:3: note:   no known conversion for argument 1 from ‘HandlePtr<int>’ to ‘HandlePtr<int>&’
try.cpp:5:3: note: HandlePtr<TYPE>::HandlePtr() [with TYPE = int]
   HandlePtr( void ) = default;
   ^
try.cpp:5:3: note:   candidate expects 0 arguments, 1 provided
try.cpp:20:20: error: redeclaration of ‘HandlePtr<int> h’
   HandlePtr< int > h = HandlePtr< int >( x ); // also does not work
                    ^
try.cpp:19:20: error: ‘HandlePtr<int> h’ previously declared here
   HandlePtr< int > h = x; // doesn't work
Brandon
  • 483
  • 3
  • 12
  • 1
    That's not `operator=`, it's just syntax representing a copy-construction. Anyway, your constructor can't possibly work in that case because you can't bind a non-const reference to a temporary. That's one of the reasons copy-constructors should take their argument by const reference. – chris Apr 12 '14 at 02:13
  • even making the ctor take a const reference won't make it take that one. – Brandon Apr 12 '14 at 02:28
  • 1
    The question has been answered in [Error: Conversion to non-scalar type](http://stackoverflow.com/questions/16179592/error-conversion-to-non-scalar-type). – R Sahu Apr 12 '14 at 02:40
  • @zeroth, Good point, your constructor that accepts the `int` in the first place takes a temporary by non-const reference. – chris Apr 12 '14 at 02:45

1 Answers1

2

You're overlooking that in the declaration:

T t = u;

this is not the assignment operator. t = u; is not a sub-expression of a declaration. The only expression here is u; and the result of evaluating the expression u is used as initializer for the object t being declared.

If u has type T, then t is copy-constructed from u.

If u does not have type T, then u first needs to be converted to type T. This creates an rvalue of type T.

You do not have any constructors that accept an rvalue, so T t = u;, and the identical T t = T(u); both fail. However, T t(u) succeeds because no rvalue is created; the value u is used as argument to the constructor T(U &).

Simplified code example:

struct T
{
    T(int &);
    T(T&);
    T();
    T &operator=(int &);
};

int main()
{
    int x = 5;
    T g(x);   // OK, T(int &)
    T g2(5);   // fail, looks for T(int const &)
    T i;      // OK, T()
    i = x;    // OK, T::operator=(int&)
    T h3 = i; // OK, T(T&)
    T h1 = T(x);    // fail, looks for T(T const &)
    T h2 = x;       // fail, identical to previous line 
}

Normally you should use const & as the parameter for copy-constructors and assignment operators; then all of these "fail" cases become "OK" , as an rvalue can be bound to a const reference.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • Well, that explains it. I intentionally didn't make the copy ctor take a const reference because due to the intended design of the class, it must copy a member by non const-reference. – Brandon Apr 12 '14 at 14:09