2

There's a problem I have with proxy objects and move semantics. They don't always combine very well. In my container class I have an overloaded operator[] that employs lazy element creation within the container. So operator[] returns a proxy object that keeps either a reference to an existing element of container or a place for the potential new element. The idea is that whenever proxy object is accessed in a specific manner or when something is assigned to it, a new element is automatically created in the container in that place.

The problem is in the way the proxy object is returned:

Proxy operator [](std::string name);

I am forced to return by value because I want the caller to manage the lifetime of proxy. The problem with returning by value is that the result of operator [] call is a prvalue, meaning that it will always be moved whenever function is called to initialize or assign a new object:

/// Element of container identified by name "foo" is moved from.
Element foo(container["foo"]);
/// Same with bar...
foo = container["bar"];

I want my proxy to be move assignable to an element, to have a smoth developer-experience. But I want it to be in a usual, expected fashion, like this:

/// Element is copied
Element foo1 = container["foo"];
/// Element is moved
Element foo2 = std::move(container["foo"]);

For all I know, it can be achieved through a second proxy object MovableProxy. According to this strategy, I want to configure Proxy in such a way that it can only be copied. Then I want to specialize std::move for Proxy type so it will return MovableProxy when supplied with a rvalue or lvalue of Proxy:

namespace std
{

MovableProxy move(Proxy & foo)
{
    ...
}

MovableProxy move(Proxy && foo)
{
    ...
}

} // namespace std

So, how bad is the idea of specializing template function std::move for my own class? And what awful pitfalls are waiting around the corner?

Rodia
  • 1,407
  • 8
  • 22
  • 29
GreenScape
  • 7,191
  • 2
  • 34
  • 64
  • 1
    You are explicitly allowed to specialize templates from namespace `std` in most cases. [Link](http://en.cppreference.com/w/cpp/language/extending_std) – François Andrieux Feb 17 '17 at 18:50
  • @FrançoisAndrieux while true, what he's doing isn't actually specializing. He's adding an overload to namespace `std`, which is disallowed. – Mooing Duck Feb 17 '17 at 19:42
  • I'm a little confused about what you want `foo = container["bar"];` to do to the `container`. Does it insert a default value and assign to `foo` as a copy? – Mooing Duck Feb 17 '17 at 19:45
  • @MooingDuck, if element by the name `"bar"` exists, it's value will be copied to `foo`, otherwise the default value of *element* will be assigned to `foo`. The thing is, *element* is a special type that has its own representation of `null`, that's what the *default value* is. – GreenScape Feb 17 '17 at 20:03
  • @GreenScape: right, but the question is: after `foo = container["bar"]`, does `container` contain a default value for `"bar"`, or does it still have that spot empty? – Mooing Duck Feb 18 '17 at 01:01
  • @MooingDuck, seeing what i wrote (*The idea is that whenever proxy object is accessed in a specific manner or when something is assigned to it, a new element is automatically created in the container in that place.*) it is obvious that no. Only when the proxy is modified in specific fashion the element is created in the container. – GreenScape Feb 22 '17 at 12:06

2 Answers2

4

Your code is an example of overloading move, not specializing it, and that is never legal.

Specializing most templates in std is legal so long as your specialization follows whatever specification the std has for the template in question. Overloading is, however, not the same as specialization.

Overloading is against the rules of interoducing new symbols into std. It makes your program ill-formed, no diagnostic required.


I would possibly consider writing a notstd::move that forwards to std::move and has overloads for extra types, or enables ADL lookup of moving...

namespace notstd{
  namespace adl_helper{
     template<class T>
     decltype(auto) mover(T&&){
       using std::move;
       // finds both std::move and any move in T's namespace:
       return move(std::forward<T>(t));
     }
  }
  namespace adl_blocker {
    // will not be found by types in notstd via ADL:
    template<class T>
    decltype(auto) move(T&& t){
      return ::notstd::adl_helper::mover(std::forward<T>(t));
    }
  }
  // import into notstd so it can be named
  // via notstd::move:
  using namespace ::notstd::adl_blocker;
}

Or somesuch.

Now friend move would be invoked by notstd::move(x) in place of std::move if it is a better match.


It turns out you cannot rewrite this as a specialization because you want to change the return type. std::move is defined as:

template< class T >
constexpr typename std::remove_reference<T>::type&& move( T&& t )

in C++14. Your specilization must agree with the above return type.

We could try to treat remove_reference as a trait to change the return type (bad idea!), but that is illegal under the standard because such specialization would violate the requirements std puts on remove_reference.

In theory, you could write this:

namespace std {
  template<>
  constexpr typename std::remove_reference<Proxy>::type&& move( Proxy&& t ) {
    t.set_move_flag = true;
    return static_cast<Proxy&&>(t);
  }
  template<>
  constexpr typename std::remove_reference<Proxy>::type&& move( Proxy const&& t ) {
    return static_cast<Proxy const&&>(t);
  }
  template<>
  constexpr typename std::remove_reference<Proxy&>::type&& move( Proxy& t ) {
    t.set_move_flag = true;
    return static_cast<Proxy&&>(t);
  }
  template<>
  constexpr typename std::remove_reference<Proxy const&>::type&& move( Proxy const& t ) {
    return static_cast<Proxy const&&>(t);
  }
}

but that is so horrible that I'm reluctant to even post it here on the off chance you try to use it. (I don't know if the above code contains errors, fundamental or not, but that is the closest I can think of to a possibly working approach.)

T.C.
  • 133,968
  • 17
  • 288
  • 421
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • As I said in the question, I don't want to affect developer-experience. `notstd::move` is not really obvious and does affect developer-experience. – GreenScape Feb 17 '17 at 19:08
  • 1
    @GreenScape Well, we don't always get what we want. – Yakk - Adam Nevraumont Feb 17 '17 at 19:11
  • 3
    "You could rewrite those as specializations." You can't. A specialization can't change the signature. Also, I think the namespaces got a little messed up. – T.C. Feb 17 '17 at 19:14
  • @T.C. And workaround using traits return value doesn't work. I don't see the namespace error, I added comments explaining the insanity? – Yakk - Adam Nevraumont Feb 17 '17 at 19:33
  • The last solution is very close to what I want. But, as you yourself said, it would be a crazy idea to implement. And we cannot change type in specialization. And we also cannot deifine functions `std`. So unfortunate. As you correctly said, we don't alwasy get what we want. – GreenScape Feb 17 '17 at 19:42
3

This behavior you want is not normally what std::move does. All it does is cast a lvalue of type T to a rvalue of T so that it can be moved from by the move constructor or move assignment operator. It does not do any move operation itself. You should never have to specialize std::move. You provide your movement functionality with a move constructor and move assignment operator.

As far as adding specializations go in general that has been answered here: Adding template specialization in std namespace

Community
  • 1
  • 1
NathanOliver
  • 171,901
  • 28
  • 288
  • 402