1

Is it possible to have a struct A with a move constructor for itself and some constructor which can move from other types (e.g struct B ) but with having a template deduction going on such that the type B is not hardcoded directly as another move constructor:

 struct A{
      A()= default;
      A(A&&a){ /* A's move constructor */ }

      template<typename T>
      A(T&&t){ 
        /* (not a move constructor! by std., matches also lvalues) 

        move from t
        (meta programming to check if we can move the type) 
        */
      } 


}

struct B{};

the problem with the above is that

B b;
A a(std::move(b)); // select the templated constructor (which moves)

A a(b);  // selects the same copy constructor (which moves but we do not want!!

How does one achieve this?

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
Gabriel
  • 8,990
  • 6
  • 57
  • 101
  • note that `T&&t` is a *forwarding reference*, it allows `T` to deduce to reference types (unlike `A(B&& b`)` ). So this constructor will actually also match lvalues. – M.M Apr 24 '15 at 11:59
  • thats the exact problem I have how to distinguish this, but with keeping the template deduction process going on...? – Gabriel Apr 24 '15 at 12:01
  • There's probably some sfinae trick but I can't think of anything. It would be a subject for another question ! – M.M Apr 24 '15 at 12:03
  • 4
    Argggghhhh your question changed. FFS. – Lightness Races in Orbit Apr 24 '15 at 12:03
  • 3
    @Gabriel I'd strongly suggest rolling back to the original question and posting the new question separately, what you have done is just annoying – M.M Apr 24 '15 at 12:04
  • sorry I posted to early and was not finished, I am sorry, giving you anyway thumbs up for your answer! – Gabriel Apr 24 '15 at 12:07
  • I need to take more care befor posting, next time! – Gabriel Apr 24 '15 at 12:12
  • What constructor should `A a(b)` select? Should it be an error? Should there be a different constructor that matches it? – Yakk - Adam Nevraumont Apr 24 '15 at 13:41

3 Answers3

4

The cleanest solution is to create a better constructor for lvalues, then =delete it:

struct A{
  A()= default;
  A(A&&a){ /* A's move constructor */ }

  template<class T>
  A(T&&t){ 
  } 
  template<class T>
  A(T const&&)=delete;
  template<class T>
  A(T&t)=delete;
};

under this method, A a(b); will fail to compile.

If you want lvalues to follow a different path, then:

struct A{
  A()= default;
  A(A&&a){ /* A's move constructor */ }

  template<class T>
  A(T&&t){ 
    // rvalues end up here
  } 
  template<class T>
  A(T const&&t):A(t) {} // forward to lvalue ctor
  template<class T>
  A(T&t){
    // lvalues end up here
  }
};

the use of SFINAE is probably only worth the complexity if you don't know which of these two options you are going to want -- ie, where you are inheriting constructors from elsewhere.

There is a final corner case -- A(T const&&) (and A(A const&&)) which can crop up in some corner cases. I made it act like an lvalue ctor, as you cannot "move from" a T const&& usually. (unless the guts of the T are mutable, and even then I'd consider it a bad idea).

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Isn't it enough to just delete the T& constructor? Wouldn't it also work for const T&? – Yam Marcovic Apr 24 '15 at 13:50
  • @Yam `T const&&` will match `T&&` better than `T&`. `T const&&` is somewhat strange, but for example `int const foo() { return 3; }` has a `const` rvalue return type. It is strange and rare enough that you could probably neglect to think about it and not see a problem from it for years or more in a large, active code base, and when you did see it happen it would probably be the symptom of some other code being pear-shaped. But a more concrete example, a `tuple` being passed as an rvalue: `std::get<0>(std::forward(tup))` will be a `int const&&` I think. – Yakk - Adam Nevraumont Apr 24 '15 at 14:04
  • @Yakk For some reasons, the type of `foo()` is not `int const`, but `int` (since `int` is a fundamental type, see [expr]/6). But your argument works for class types. – dyp Apr 24 '15 at 14:09
  • @Yakk Oops, didn't see that second & there, I thought you were deleting both T& and const T&. Sorry. – Yam Marcovic Apr 24 '15 at 14:14
  • @dyp C++ is, if nothing less, a silly language. :) My bad, I should never ever use `int` when talking about lvalues and rvalues and `const` unless I want to talk about the exceptions that built-in types entail. Almost as bad as if I made a function returning an array. >_> <_ – Yakk - Adam Nevraumont Apr 24 '15 at 14:14
  • (Well it fits into the weird scheme of prvalues of fundamental types being values, not objects. For values, cv-qualification doesn't make sense. Binding prvalues of fundamental type to a reference creates a new object. I can't see a way to observe this, though.) – dyp Apr 24 '15 at 14:20
2

If you want the constructor template to accept arbitrary rvalues, you can use enable_if:

template <
  class T,
  class Sfinae = typename std::enable_if<!std::is_lvalue_reference<T>::value>::type
>
A(T &&t)

This will enable the constructor only if T was not deduced to an lvalue reference, which is equivalent to saying that the argument was not an lvalue.

As @dyp mentioned in the comments, it can also be expressed more explicitly:

template <
  class T,
  class Sfinae = typename std::enable_if<std::is_rvalue_reference<T&&>::value>::type
>
A(T &&t)
Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • 1
    awesome , so in this way SFINAE helps to only have rvalues accepted :-) that was I looking for – Gabriel Apr 24 '15 at 12:05
  • SFINAE works for default template argument? `typename std::enable_if<!std::is_lvalue_reference::value>::type* = nullptr` seems to be the commonly-used pattern. – Lingxi Apr 24 '15 at 12:53
  • Anyway, a compiler error happens in either case. But the error message generated by using `typename std::enable_if<!std::is_lvalue_reference::value>::type* = nullptr` should be more suggestive. – Lingxi Apr 24 '15 at 13:02
  • @Lingxi Yes, SFINAE works for template arguments just fine. I prefer this one because to me, it's more obvious than buried in the function parameters; but you can use whichever you like more. Clang, for example, generates really nice messages for both. If you really care about the error message, you could add an overload which would accept the lvalue references only, and `static_assert` inside it. – Angew is no longer proud of SO Apr 24 '15 at 13:25
  • 1
    Instead of using a negative check, why not test `std::is_rvalue_reference::value`? – dyp Apr 24 '15 at 13:45
  • @dyp Because it didn't occur to me to check something other than bare `T`. Yours works perfectly fine, too. – Angew is no longer proud of SO Apr 24 '15 at 13:50
  • If you like it more, you can actually remove "Sfinae" and make it a nameless parameter, to make its sfinaeishness implicit, but just as obvious. :) – Yam Marcovic Apr 24 '15 at 14:04
0

Simply take a B&& in the constructor, exactly as you did for A.

Puppy
  • 144,682
  • 38
  • 256
  • 465