[cfe-dev] Broken type deduction for pair of reference_wrappers?

Howard Hinnant hhinnant at apple.com
Fri Aug 10 12:28:21 PDT 2012


So there are several misconceptions going on here.  Richard noted earlier today that this really doesn't have anything to do with reference_wrapper:

#include <functional>
#include <algorithm>
#include <cstdlib>
#include <iostream>
#include <cxxabi.h>

template <typename T>
std::unique_ptr<char, void(*)(void*)>
type_name(const T&)
{
    return std::unique_ptr<char, void(*)(void*)>
           (
                __cxxabiv1::__cxa_demangle(typeid(T).name(), nullptr,
                                           nullptr, nullptr),
                std::free
           );
}

struct MyType {virtual void foo() = 0;};
struct X : MyType {virtual void foo() {}};

int main()
{
    X a, b;
    MyType& one = a;
    MyType& two = b;
    auto mypair = std::make_pair(std::ref(one), std::ref(two));
    std::cout << type_name(mypair).get() << '\n';
}

std::__1::pair<MyType&, MyType&>

I.e. make_pair converts reference_wrapper<T> into T&.

So the question now becomes: Can you use the generic std::swap on two lvalues of MyType.  It is easy to see if you try out a simplified home-made swap:

#include <functional>
#include <algorithm>
#include <cstdlib>
#include <iostream>

template <class T>
void
my_swap(T& x, T& y)
{
    T tmp(std::move(x));
    x = std::move(y);
    y = std::move(tmp);
}

struct MyType {virtual void foo() = 0;};
struct X : MyType {virtual void foo() {}};

int main()
{
    X a, b;
    MyType& one = a;
    MyType& two = b;
    auto mypair = std::make_pair(std::ref(one), std::ref(two));
    my_swap(mypair.first, mypair.second);
}

test.cpp:64:7: error: variable type 'MyType' is an abstract class
    T tmp(std::move(x));
      ^
test.cpp:82:5: note: in instantiation of function template specialization 'my_swap<MyType>' requested here
    my_swap(mypair.first, mypair.second);
    ^
1 error generated.

Or perhaps more concisely:

    static_assert(std::is_move_constructible<MyType>::value, "MyType is not move constructible");

Fires:

test.cpp:79:5: error: static_assert failed "MyType is not move constructible"
    static_assert(std::is_move_constructible<MyType>::value, "MyType is not move constructible");
    ^             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.

Otoh, std::reference_wrapper<MyType> is both MoveConstructible and MoveAssignable.  And that's why when you assign the pair<MyType&, MyType&> returned from make_pair back into a std::pair<std::reference_wrapper<MyType>,std::reference_wrapper< MyType >>, it works.

Howard

On Aug 10, 2012, at 3:06 PM, Michael Price - Dev <michael.b.price.dev at gmail.com> wrote:

> Ah, yes, I jumped the gun when I saw && and delete in the same signature. I believe you are correct and it makes more sense now.
> 
> Sent from my iPhone
> 
> On Aug 10, 2012, at 1:50 PM, Ahmed Charles <acharles at outlook.com> wrote:
> 
>> The deleted constructor is from T&&, so it is not a move constructor, but just a regular one.
>> 
>> The MoveConstructible and MoveAssignable requirements are designed to allow the standard containers to be specified. Thus, having move as a superset of copy makes sense and allows for less verbosity without loosing too much clarity. It is also the case that attempting to move something that only has copy operations will work, since copy constructors accept a superset of what move constructors accept.
>> 
>> The standard is right, it's just a matter of communication and understanding. Though I'm not sure if I've explained clearly enough yet.
>> From: James Dennett
>> Sent: 8/9/2012 11:37 PM
>> To: Michael Price
>> Cc: Richard Smith; Clang Developers List
>> Subject: Re: [cfe-dev] Broken type deduction for pair of reference_wrappers?
>> 
>> On Thu, Aug 9, 2012 at 10:35 PM, Michael Price
>> <michael.b.price.dev at gmail.com> wrote:
>> >
>> > On Thu, Aug 9, 2012 at 11:48 PM, James Dennett <james.dennett at gmail.com>
>> > wrote:
>> >>
>> >> On Thu, Aug 9, 2012 at 9:41 PM, Michael Price
>> >> <michael.b.price.dev at gmail.com> wrote:
>> >> > On Thu, Aug 9, 2012 at 10:02 PM, James Dennett <james.dennett at gmail.com>
>> >> > wrote:
>> >> >>
>> >> >> On Thu, Aug 9, 2012 at 7:16 PM, Michael Price
>> >> >> <michael.b.price.dev at gmail.com> wrote:
>> >> >> > On Thu, Aug 9, 2012 at 5:48 PM, Richard Smith <richard at metafoo.co.uk>
>> >> >> > wrote:
>> >>
>> > <SNIP>
>> >>
>> >> >> >>
>> >> >> >> your values can't be swapped, because MyType is not move
>> >> >> >> constructible,
>> >> >> >> because it's an abstract class type. The specification for 'swap'
>> >> >> >> says:
>> >> >> >>
>> >> >> >> Requires: Type T shall be MoveConstructible (Table 20) and
>> >> >> >> MoveAssignable
>> >> >> >> (Table 22).
>> >> >> >>
>> >> >> > Why does it compile (and it does in fact appear to execute correctly)
>> >> >> > if
>> >> >> > mypair is a pair of reference_wrappers then?
>> >> >>
>> >> >> Because reference_wrapper is CopyConstructible and CopyAssignable
>> >> >> (hence MoveConstructible and MoveAssignable), which is why it's a good
>> >> >> match for what std::pair needs.
>> 
>> In hindsight: The standard makes this claim, but I think it's wrong.
>> 
>> >> >> > I don't think that
>> >> >> > reference_wrapper is MoveConstrucible and MoveAssignable.
>> >> >>
>> >> >> Why not?
>> >> >
>> >> > The following web page mentions CopyConstructible and CopyAssignable,
>> >> > but
>> >> > not the move equivalents (granted, it is not authoritative).
>> >> > http://en.cppreference.com/w/cpp/utility/functional/reference_wrapper
>> >> >
>> >> > Why would copyable imply movable? You can definitely have types that are
>> >> > copyable but not movable as they can be deleted independently.
>> >>
>> >> The standard specifies the requirements for CopyConstructible and
>> >> CopyAssignable as refinements of  MoveConstructible and MoveAssignable
>> >> respectively (see tables 20-23).
>> >>
>> > 20.8.3 Class template reference_wrapper [refwrap]
>> >
>> > namespace std {
>> >   template <class T> class reference_wrapper {
>> >   public :
>> > ...
>> >     // construct/copy/destroy
>> >     reference_wrapper(T&) noexcept;
>> >     reference_wrapper(T&&) = delete; // do not bind to temporary objects
>> >     reference_wrapper(const reference_wrapper<T>& x) noexcept;
>> >     // assignment
>> >     reference_wrapper& operator=(const reference_wrapper<T>& x) noexcept;
>> > ...
>> >   };
>> > }
>> >
>> > Clearly that thing cannot be move constructed.  Maybe we are talking past
>> > each other at this point.  I feel that I still don't quite understand how a
>> > type could be considered MoveConstructible, yet have a deleted move
>> > constructor, but I'm willing to chalk that up to language-lawyer-ese.
>> 
>> You have a point, and I think that this is a defect in the standard.
>> reference_wrapper is documented as being
>> CopyConstructible/CopyAssignable, but it is not.  CopyConstructible
>> implies MoveConstructible which implies that construction from an
>> rvalue is permitted, but reference_wrapper disables that, so it's not
>> MoveConstructible and hence no longer meets the CopyConstructible
>> requirements (though its predecessor did in a world before move
>> semantics).
>> 
>> > Thanks for your help.  I guess it's back to specifying the full type instead
>> > of using the shiny 'auto' keyword.
>> 
>> Yes, if the special-casing of reference wrapper is getting in your way
>> then that's the way around it.
>> 
>> -- James
>> _______________________________________________
>> cfe-dev mailing list
>> cfe-dev at cs.uiuc.edu
>> http://lists.cs.uiuc.edu/mailman/listinfo/cfe-dev
> _______________________________________________
> cfe-dev mailing list
> cfe-dev at cs.uiuc.edu
> http://lists.cs.uiuc.edu/mailman/listinfo/cfe-dev




More information about the cfe-dev mailing list