Topic: Template Constructors vs. Copy Constructors


Author: "Paul Mensonides" <pmenso57@comcast.net>
Date: Mon, 19 Apr 2010 11:36:55 CST
Raw View
In the following code, which constructor should be used to perform the copy?

-----

#include <iostream>

class A {
   public:
       A() { }
       A(const A&) {
           std::cout << "A(const A&)\n";
       }
       template<class T> A(T&&) {
           std::cout << "A(T&&)\n";
       }
};

int main() {
   A x, y(x);
   return 0;
}

-----

GCC 4.4.3 and 4.5 both use the template because 'x' is an non-const lvalue.
Is this correct?  I'm finding that I'm constantly having to SFINAE such
"forwarding" constructors out of existence during a copy.  E.g.

-----

#include <string>
#include <utility>

class B {
   public:
       template<class T> B(T&& s)
           : s_(std::forward<T>(s)) { }
   private:
       std::string s_;
};

int main() {
   B x("abc"), y(x); // epic fail
   return 0;
}

-----

#include <string>
#include <type_traits>
#include <utility>

template<class T, class U> struct disable_copy
   : std::enable_if<
       !std::is_base_of<
           T,
           typename std::remove_reference<U>::type
       >::value
   > { };

class C {
   public:
       template<class T> C(T&& s, typename disable_copy<C, T>::type* = 0)
           : s_(std::forward<T>(s)) { }
   private:
       std::string s_;
};

int main() {
   C x("abc"), y(x); // works
   return 0;
}

-----

So, I guess the root question is that although template constructors never
prevent the implicit copy constructor from being generated, should such
constructors participate in overload resolution during a copy?  Similarly
with copy assignment?

Otherwise, such "forwarding" constructors are a constant nuisance since they
nearly always require the heavy hand of SFINAE to manually control.

Regards,
Paul Mensonides



--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use
mailto:std-c++@netlab.cs.rpi.edu<std-c%2B%2B@netlab.cs.rpi.edu>
]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Anthony Williams <anthony.ajw@gmail.com>
Date: Wed, 21 Apr 2010 00:21:47 CST
Raw View
"Paul Mensonides" <pmenso57@comcast.net> writes:

> In the following code, which constructor should be used to perform the copy?
>
> -----
>
> #include <iostream>
>
> class A {
>    public:
>        A() { }
>        A(const A&) {
>            std::cout << "A(const A&)\n";
>        }
>        template<class T> A(T&&) {
>            std::cout << "A(T&&)\n";
>        }
> };
>
> int main() {
>    A x, y(x);
>    return 0;
> }
>
> -----
>
> GCC 4.4.3 and 4.5 both use the template because 'x' is an non-const lvalue.
> Is this correct?

Yes, I believe so. The template deduces T to be A&, so the constructor
is A(A&), which is a better match than A(const A&).

> I'm finding that I'm constantly having to SFINAE such
> "forwarding" constructors out of existence during a copy.  E.g.

You could just implement the A(A&) constructor as well, since
non-template constructors are preferred to template ones.

class A {
  public:
      A() { }
      A(const A&) {
          std::cout << "A(const A&)\n";
      }
   A(A&)
   {
       std::cout<<"A(A&)\n";
   }

   template<class T> A(T&&) {
          std::cout << "A(T&&)\n";
      }
};

You could even use the new delegating constructors feature to avoid the
duplicate implementation.

> So, I guess the root question is that although template constructors never
> prevent the implicit copy constructor from being generated, should such
> constructors participate in overload resolution during a copy?  Similarly
> with copy assignment?

You get the same effect with C++98 and a template constructor that takes
a non-const reference:

class B {
  public:
      B() { }
      B(const B&) {
          std::cout << "B(const B&)\n";
      }

   template<class T> B(T&) {
          std::cout << "B(T&)\n";
      }
};

int main() {
  B x2, y2(x2);
  return 0;
}

This prints "B(T&)".

However, the use of perfect forwarding makes it more likely that such
code is written. I'm not sure that preventing it from participating in
overload resolution is necessarily the answer, but preferring a copy or
move constructor for class X where the rhs is an instance of X or a
class derived from X might be desirable.

Anthony
--
Author of C++ Concurrency in Action     http://www.stdthread.co.uk/book/
just::thread C++0x thread library             http://www.stdthread.co.uk
Just Software Solutions Ltd       http://www.justsoftwaresolutions.co.uk
15 Carrallack Mews, St Just, Cornwall, TR19 7UL, UK. Company No. 5478976

[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: litb <Schaub-Johannes@web.de>
Date: Wed, 21 Apr 2010 00:22:43 CST
Raw View
On 19 Apr., 19:36, "Paul Mensonides" <pmens...@comcast.net> wrote:
> In the following code, which constructor should be used to perform the copy?
>
> -----
>
> #include <iostream>
>
> class A {
>    public:
>        A() { }
>        A(const A&) {
>            std::cout << "A(const A&)\n";
>        }
>        template<class T> A(T&&) {
>            std::cout << "A(T&&)\n";
>        }
>
> };
>
> int main() {
>    A x, y(x);
>    return 0;
>
> }
>
> -----
>
> GCC 4.4.3 and 4.5 both use the template because 'x' is an non-const lvalue.
> Is this correct?  I'm finding that I'm constantly having to SFINAE such
> "forwarding" constructors out of existence during a copy.  E.g.
>
> -----
>
> #include <string>
> #include <utility>
>
> class B {
>    public:
>        template<class T> B(T&& s)
>            : s_(std::forward<T>(s)) { }
>    private:
>        std::string s_;
>
> };
>
> int main() {
>    B x("abc"), y(x); // epic fail
>    return 0;
>
> }
>
> -----
>
> #include <string>
> #include <type_traits>
> #include <utility>
>
> template<class T, class U> struct disable_copy
>    : std::enable_if<
>        !std::is_base_of<
>            T,
>            typename std::remove_reference<U>::type
>        >::value
>    > { };
>
> class C {
>    public:
>        template<class T> C(T&& s, typename disable_copy<C, T>::type* = 0)
>            : s_(std::forward<T>(s)) { }
>    private:
>        std::string s_;
>
> };
>
> int main() {
>    C x("abc"), y(x); // works
>    return 0;
>
> }
>
> -----
>
> So, I guess the root question is that although template constructors never
> prevent the implicit copy constructor from being generated, should such
> constructors participate in overload resolution during a copy?  Similarly
> with copy assignment?
>
> Otherwise, such "forwarding" constructors are a constant nuisance since they
> nearly always require the heavy hand of SFINAE to manually control.
>

In my opinion, the core issue here is the abuse of rvalue references.
That template specializations participate in overload resolution for
copying a class object is fine, i think. If you disable it here, next
time someone else shows up and complains about another situation where
he wants to disable perfect forwarding semantics for "T&&".

It's too late to unabuse rvalue references as forwarding vehicles,
sadly :(


--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Nikolay Ivchenkov <tsoae@mail.ru>
Date: Wed, 21 Apr 2010 12:19:39 CST
Raw View
On 21 Apr, 10:21, Anthony Williams <anthony....@gmail.com> wrote:
> > GCC 4.4.3 and 4.5 both use the template because 'x' is an non-const
lvalue.
> > Is this correct?
>
> Yes, I believe so. The template deduces T to be A&, so the constructor
> is A(A&), which is a better match than A(const A&).

According to C++03 - 12.8/3 or N3092 - 12.8/7,
"A member function template is never instantiated to perform the copy
of a class object to an object of its class type."

What does it mean? And is the following program well-formed?

   struct X
   {
       X() {}
       X(X &) {}
       X(X *) {}
       operator X *() { return this; }
       template <class T>
           X(T) {}
   };

   void f(X) {}

   int main()
   {
       f(X());
   }

Note: VC++9.0 and GNU C++ 4.5 compile this code successfully, but
Comeau C++ 4.3.10.1 issues the following diagnostic message:
"error: copy constructor for class "X" may not have a parameter of
type "X"".

--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use
mailto:std-c++@netlab.cs.rpi.edu<std-c%2B%2B@netlab.cs.rpi.edu>
]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Paul Mensonides" <pmenso57@comcast.net>
Date: Wed, 21 Apr 2010 12:19:09 CST
Raw View
"litb" <Schaub-Johannes@web.de> wrote in message
news:b7ce85fb-7ace-4586-a93b-6d003809c815@c1g2000vbc.googlegroups.com...


> In my opinion, the core issue here is the abuse of rvalue references.
> That template specializations participate in overload resolution for
> copying a class object is fine, i think. If you disable it here, next
> time someone else shows up and complains about another situation where
> he wants to disable perfect forwarding semantics for "T&&".
>
> It's too late to unabuse rvalue references as forwarding vehicles,
> sadly :(

I'm beginning to get wary about this also, mainly regarding const
correctness guarantees.

Regards,
Paul Mensonides



--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use
mailto:std-c++@netlab.cs.rpi.edu<std-c%2B%2B@netlab.cs.rpi.edu>
]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Paul Mensonides" <pmenso57@comcast.net>
Date: Wed, 21 Apr 2010 12:29:19 CST
Raw View
"Anthony Williams" <anthony.ajw@gmail.com> wrote in message
news:874oj64i0z.fsf@dell.justsoftwaresolutions.co.uk...

> You could even use the new delegating constructors feature to avoid the
> duplicate implementation.

Yes.

>> So, I guess the root question is that although template constructors
>> never
>> prevent the implicit copy constructor from being generated, should such
>> constructors participate in overload resolution during a copy?  Similarly
>> with copy assignment?
>
> You get the same effect with C++98 and a template constructor that takes
> a non-const reference:
>
> class B {
>  public:
>      B() { }
>      B(const B&) {
>          std::cout << "B(const B&)\n";
>      }
>
>   template<class T> B(T&) {
>          std::cout << "B(T&)\n";
>      }
> };
>
> int main() {
>  B x2, y2(x2);
>  return 0;
> }
>
> This prints "B(T&)".

Yes.  But, as you say, this sort of scenario is unlikely in C++98 (since it
the de facto standard to pass by reference to const unless mutation is
required).

> However, the use of perfect forwarding makes it more likely that such
> code is written. I'm not sure that preventing it from participating in
> overload resolution is necessarily the answer, but preferring a copy or
> move constructor for class X where the rhs is an instance of X or a
> class derived from X might be desirable.

At least it is consistent with the way that C++98 already works with T&.

Regards,
Paul Mensonides



--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use
mailto:std-c++@netlab.cs.rpi.edu<std-c%2B%2B@netlab.cs.rpi.edu>
]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: rogero <roger.orr@gmail.com>
Date: Wed, 21 Apr 2010 12:29:18 CST
Raw View
On Apr 19, 6:36 pm, "Paul Mensonides" <pmens...@comcast.net> wrote:
> In the following code, which constructor should be used to perform the
copy?
>
> -----
>
> #include <iostream>
>
> class A {
>    public:
>        A() { }
>        A(const A&) {
>            std::cout << "A(const A&)\n";
>        }
>        template<class T> A(T&&) {
>            std::cout << "A(T&&)\n";
>        }
>
> };
>
> int main() {
>    A x, y(x);
>    return 0;
>
> }
>
> -----
>
> GCC 4.4.3 and 4.5 both use the template because 'x' is an non-const
lvalue.
> Is this correct?

I don't think so.

>From N3000, 12.8p2 Copying class objects:

114) Because a template constructor or a constructor whose first
parameter is an rvalue reference is never a copy constructor, ...

Roger.


--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use
mailto:std-c++@netlab.cs.rpi.edu<std-c%2B%2B@netlab.cs.rpi.edu>
]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Anthony Williams <anthony.ajw@gmail.com>
Date: Wed, 21 Apr 2010 19:18:20 CST
Raw View
Nikolay Ivchenkov <tsoae@mail.ru> writes:

> On 21 Apr, 10:21, Anthony Williams <anthony....@gmail.com> wrote:
>> > GCC 4.4.3 and 4.5 both use the template because 'x' is an non-const
> lvalue.
>> > Is this correct?
>>
>> Yes, I believe so. The template deduces T to be A&, so the constructor
>> is A(A&), which is a better match than A(const A&).
>
> According to C++03 - 12.8/3 or N3092 - 12.8/7,
> "A member function template is never instantiated to perform the copy
> of a class object to an object of its class type."

D'oh! Of course that takes precedence. The example in N3092 even covers
that:

struct S {
   template<typename T> S(T);
   template<typename T> S(T&&);
   S();
};

S f();
const S g;
void h() {
   S a( f() ); // does not instantiate member template;
               // uses the implicitly generated move constructor
   S a(g);     // does not instantiate the member template;
               // uses the implicitly generated copy constructor
}

> What does it mean? And is the following program well-formed?
>
>    struct X
>    {
>        X() {}
>        X(X &) {}
>        X(X *) {}
>        operator X *() { return this; }
>        template <class T>
>            X(T) {}
>    };
>
>    void f(X) {}
>
>    int main()
>    {
>        f(X());
>    }
>
> Note: VC++9.0 and GNU C++ 4.5 compile this code successfully, but
> Comeau C++ 4.3.10.1 issues the following diagnostic message:
> "error: copy constructor for class "X" may not have a parameter of
> type "X"".

I think the answer is "yes", but for a different reason: the "copy"
should go via the conversion to X* and the X(X*) constructor. An rvalue
cannot bind to the X(X&) copy constructor, so it is ruled out. This sort
of technique is used for move semantics in C++03 code.

Anthony
--
Author of C++ Concurrency in Action     http://www.stdthread.co.uk/book/
just::thread C++0x thread library             http://www.stdthread.co.uk
Just Software Solutions Ltd       http://www.justsoftwaresolutions.co.uk
15 Carrallack Mews, St Just, Cornwall, TR19 7UL, UK. Company No. 5478976

[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use
mailto:std-c++@netlab.cs.rpi.edu<std-c%2B%2B@netlab.cs.rpi.edu>
]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Nikolay Ivchenkov <tsoae@mail.ru>
Date: Fri, 23 Apr 2010 11:00:46 CST
Raw View
On 22 Apr, 05:18, Anthony Williams <anthony....@gmail.com> wrote:
> Nikolay Ivchenkov <ts...@mail.ru> writes:
>
> > According to C++03 - 12.8/3 or N3092 - 12.8/7,
> > "A member function template is never instantiated to perform the copy
> > of a class object to an object of its class type."
>
> D'oh! Of course that takes precedence. The example in N3092 even covers
> that:
>
> struct S {
>    template<typename T> S(T);
>    template<typename T> S(T&&);
>    S();
>
> };
>
> S f();
> const S g;
> void h() {
>    S a( f() ); // does not instantiate member template;
>                // uses the implicitly generated move constructor
>    S a(g);     // does not instantiate the member template;
>                // uses the implicitly generated copy constructor
>
>
>
> }
> > What does it mean?

This example in the specification does not cover cases when the
overload resolution would select a function template specialization:

   #include <iostream>

   struct X
   {
       X() {}
       X(X &) {}
       template <class T>
           X(T const &)
               { std::cout << "X(T const &)\n"; }
   };

   struct Y
   {
       Y() {}
       template <class T>
           Y(T &&)
               { std::cout << "Y(T &&)\n"; }
   };

   int main()
   {
       X x = X();
       Y y1;
       Y y2 = y1;
   }

so, the question is still open.

> > And is the following program well-formed?
>
> >    struct X
> >    {
> >        X() {}
> >        X(X &) {}
> >        X(X *) {}
> >        operator X *() { return this; }
> >        template <class T>
> >            X(T) {}
> >    };
>
> >    void f(X) {}
>
> >    int main()
> >    {
> >        f(X());
> >    }
>
> > Note: VC++9.0 and GNU C++ 4.5 compile this code successfully, but
> > Comeau C++ 4.3.10.1 issues the following diagnostic message:
> > "error: copy constructor for class "X" may not have a parameter of
> > type "X"".
>
> I think the answer is "yes", but for a different reason: the "copy"
> should go via the conversion to X* and the X(X*) constructor.

The X(X *) constructor can be selected via overload resolution only
if:

- the function template X(T) is excluded from the initial set of
candiate functions, or

- the function template specialization X(T) with T = X is excluded
from the final set of candiate functions, or

- the candidate function template specialization is not a viable
function, or

- the implicit conversion sequence for the parameter of X(X *) is not
worse than that for the parameter of the viable candidate function
template specialization.

Which option is true in your opinion?

--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use
mailto:std-c++@netlab.cs.rpi.edu<std-c%2B%2B@netlab.cs.rpi.edu>
]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]