C++0X explicitly defaulted copy constructors

Discussion in 'C++' started by Marc, Nov 1, 2010.

  1. Marc

    Marc Guest

    See below for the code. A variadic argument U&&... matches a
    copy-from-lvalue better than the implicit copy constructors, so I need
    to declare a copy constructor from lvalue. And since its existence
    prevents the implicit declaration of the other two versions (from const
    ref and from rvalue), I need to declare those 2 as well. Now I don't
    want anything fancy for those and I am fine with whatever the compiler
    can generate by default, so I default the three copy/move constructors.

    Now, according to g++, I am only allowed to specify "=default" inside
    the class for 2 of them, for the one that takes an lvalue reference I
    can only do it outside the class (though I can add "inline" if I like),
    otherwise I get:

    error: ‘A::A(A&)’ declared to take non-const reference cannot be
    defaulted in the class body

    Now I am quite confused as to why that is...



    #include <iostream>
    struct A {
    A()=default;
    template<class...U> A(U&&...){
    std::cout << "NOOOOO!\n";
    }
    A(A&);
    A(A const&)=default;
    A(A &&)=default;
    };
    A::A(A&)=default;
    int main(){
    A a;
    std::cout << "---\n";
    A b{a};
    std::cout << "---\n";
    A c{(A&)a}; // same as the previous one
    std::cout << "---\n";
    A d{(A const&)a};
    std::cout << "---\n";
    A e{(A&&)a};
    }
     
    Marc, Nov 1, 2010
    #1
    1. Advertising

  2. Marc wrote:

    > See below for the code. A variadic argument U&&... matches a
    > copy-from-lvalue better than the implicit copy constructors, so I need
    > to declare a copy constructor from lvalue. And since its existence
    > prevents the implicit declaration of the other two versions (from const
    > ref and from rvalue), I need to declare those 2 as well. Now I don't
    > want anything fancy for those and I am fine with whatever the compiler
    > can generate by default, so I default the three copy/move constructors.
    >


    Note that you can employ a trick here. Your template will be a better match
    than the copy constructor only for U = "A&" and "A const". For that, you can
    make the class declaration not declare such a constructor, but introduce it
    later on immediately.

    struct A {
    A()=default;
    template<class...U> A(U&&...){
    std::cout << "NOOOOO!\n";
    }
    // note: non-template functions are preferred!
    inline A(A&, int); // does not prevent implicit A(A const&);
    inline A(A const&&, int); // does not prevent A(A&&);
    };
    A::A(A&, int = 0) {
    /* copy */
    }
    A::A(A const&&, int = 0) {
    /* copy */
    }

    This is more work than necessary, though, and I haven't tested it.
    Note that this way too, your class becomes non-trivial (it's trivial after
    the class was defined, but becomes non-trivial after the copy constructors
    are defined later on).

    You could use SFINAE for this, although I admit it is ugly to catch the
    exact case when U... has sizeof...(U) == 1 and such. The following should
    suffice, I believe (insert typename as necessary... - and I haven't tested
    it)

    struct A {
    A()=default;
    template<class U,
    class = enable_if<
    !is_same<remove_reference<U>::type const, A const>::value>
    ::type>
    A(U&&) {
    std::cout << "YEEEES!\n";
    }

    template<class...U, class = bool[sizeof...(U) > 1]>
    A(U&&...){
    std::cout << "YEEEES!\n";
    }
    };

    This way, your class will still be trivially copyable - as opposed to the
    out-of-line defaulting workaround.

    Note that if issue report http://www.open-
    std.org/jtc1/sc22/wg21/docs/cwg_active.html#1080 is rejected, then you don't
    need any precautions: A(U&&) will never be used then inplace of a move/copy
    constructor. But I doubt it.

    The reason GCC rejects your code apparently has something to do with that
    the Standard marks first-declaration-explicitly-defaulted functions
    potentially trivial (in your case, it actually is trivial). The Standard
    requires that such an explicit-defaulting must match the parameter types
    that the implicitly-declared copy constructor/move constructor would have.
     
    Johannes Schaub (litb), Nov 1, 2010
    #2
    1. Advertising

  3. Marc

    Marc Guest

    Hello, thank you for your answer.

    litb wrote:

    > Note that you can employ a trick here. Your template will be a better match
    > than the copy constructor only for U = "A&" and "A const". For that, you can
    > make the class declaration not declare such a constructor, but introduce it
    > later on immediately.
    >
    > struct A {
    > A()=default;
    > template<class...U> A(U&&...){
    > std::cout << "NOOOOO!\n";
    > }
    > // note: non-template functions are preferred!
    > inline A(A&, int); // does not prevent implicit A(A const&);
    > inline A(A const&&, int); // does not prevent A(A&&);
    > };
    > A::A(A&, int = 0) {
    > /* copy */
    > }
    > A::A(A const&&, int = 0) {
    > /* copy */
    > }


    Really, that's legal? (yes, g++ accepts it)

    > You could use SFINAE for this


    Yes, I know and may end up using it, it just seemed like a case for
    simple code (I feel like I meta-program too much already).

    > , although I admit it is ugly to catch the
    > exact case when U... has sizeof...(U) == 1 and such. The following should
    > suffice, I believe


    Maybe is_same<tuple<remove_ref<U>::type const volatile...>,tuple<A const
    volatile>> etc?
    But the idea is the same.
    (btw, I had forgotten about the array-of-size-0 trick, thanks for
    reminding me)

    > (insert typename as necessary...


    :)
    I still can't get my fingers to type typename without the compiler
    complaining first...

    > This way, your class will still be trivially copyable - as opposed to the
    > out-of-line defaulting workaround.


    Oh, I hadn't thought about that... Indeed it would be wrong to lose this
    property.

    > Note that if issue report http://www.open-
    > std.org/jtc1/sc22/wg21/docs/cwg_active.html#1080 is rejected, then you don't
    > need any precautions: A(U&&) will never be used then inplace of a move/copy
    > constructor. But I doubt it.


    I always found it strange that U&& would be used for copying lvalues but
    not rvalues. If there was a good way to make this inconsistancy
    disappear...

    > The reason GCC rejects your code apparently has something to do with that
    > the Standard marks first-declaration-explicitly-defaulted functions
    > potentially trivial (in your case, it actually is trivial). The Standard
    > requires that such an explicit-defaulting must match the parameter types
    > that the implicitly-declared copy constructor/move constructor would have.


    And since A(A&) would not be implicitly declared, I can't default it in
    the body. And if I changed A to be in the case where the one implicitly
    generated is A(A&), I guess I couldn't default A(A const&) then. It
    still seems like a "bug".
     
    Marc, Nov 1, 2010
    #3
  4. Marc wrote:

    > Hello, thank you for your answer.
    >
    > litb wrote:
    >
    >> Note that you can employ a trick here. Your template will be a better
    >> match than the copy constructor only for U = "A&" and "A const". For
    >> that, you can make the class declaration not declare such a constructor,
    >> but introduce it later on immediately.
    >>
    >> struct A {
    >> A()=default;
    >> template<class...U> A(U&&...){
    >> std::cout << "NOOOOO!\n";
    >> }
    >> // note: non-template functions are preferred!
    >> inline A(A&, int); // does not prevent implicit A(A const&);
    >> inline A(A const&&, int); // does not prevent A(A&&);
    >> };
    >> A::A(A&, int = 0) {
    >> /* copy */
    >> }
    >> A::A(A const&&, int = 0) {
    >> /* copy */
    >> }

    >
    > Really, that's legal? (yes, g++ accepts it)
    >


    Sure. See http://llvm.org/bugs/show_bug.cgi?id=5989 for some more stuff.

    >> , although I admit it is ugly to catch the
    >> exact case when U... has sizeof...(U) == 1 and such. The following should
    >> suffice, I believe

    >
    > Maybe is_same<tuple<remove_ref<U>::type const volatile...>,tuple<A const
    > volatile>> etc?
    > But the idea is the same.


    Ah for sure, that's a neat solution.
     
    Johannes Schaub (litb), Nov 2, 2010
    #4
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Jeremy Smith
    Replies:
    2
    Views:
    621
    Jeremy Smith
    Aug 3, 2006
  2. Jess
    Replies:
    5
    Views:
    642
    Ron Natalie
    Jun 7, 2007
  3. Mark Yip
    Replies:
    2
    Views:
    755
    prospercity
    Jan 29, 2008
  4. srp113
    Replies:
    3
    Views:
    495
  5. Prasoon
    Replies:
    17
    Views:
    1,121
    Bart van Ingen Schenau
    Jul 5, 2009
Loading...

Share This Page