3

I'm playing with this little snippet:

#include <tuple>

struct copy_only
{
    copy_only() = default;
    copy_only(copy_only&&) = delete;
    copy_only(const copy_only&) = default;
};

template <typename ...Ts>
void foo(Ts&& ...xs)
{
    auto t = std::make_tuple(std::forward<Ts>(xs)...);
    (void) t;
}

int main()
{
   foo(copy_only{});
}

It compiles fine with gcc7 and clang3.6, clang3.7, clang3.8 (Wandbox), and clang8.0 (macOS Sierra). It doesn't compile with clang3.9, g++6.2 (macOS Sierra) nor with clang4.0 (Wandbox). All of them complain about deleted move constructor.

It works fine with move-only types. At least on the above compilers available on Wandbox.

Is this code an example of a correct way of generic perfect forwarding into a tuple in c++14?

Przemek Kryger
  • 687
  • 4
  • 11
  • If you remove `copy_only(copy_only&&) = delete;`, that constructor won't be generated, and the copy overload can be chosen. Currently the move constructor is a better match. – Jarod42 Nov 10 '16 at 20:06
  • @Jarod42 that's correct, but why does it compile with gcc and older clang? – krzaq Nov 10 '16 at 20:08
  • @kraz: in c++1z, `auto t = ..` would do copy elision without requiring the copy "existence". – Jarod42 Nov 10 '16 at 20:14
  • 1
    @Jarod42 thanks for that insight. I've already found a solution (i.e. call forward only when `std::is_move_constructible::value == false`). Yet I'm curious why the sample above switches between compilable and non-compilable, and which is correct c++14: the newest clang or the newest gcc. – Przemek Kryger Nov 10 '16 at 20:15
  • @Jarod42 ah, it makes sense now – krzaq Nov 10 '16 at 20:17
  • Fundamentally, the writers of the C++ standard don't care about supporting this kind of pathological types. If it works, you got lucky; if it doesn't, too bad. – T.C. Nov 10 '16 at 20:29
  • But indeed there is a difference between `-stdlib=libc++` and `-stdlib=libstdc++`. [Demo](http://coliru.stacked-crooked.com/a/0bc430b7438a8e81). – Jarod42 Nov 10 '16 at 20:32
  • According to [tuple](http://en.cppreference.com/w/cpp/utility/tuple/tuple) move constructor (9) requires *"that std::is_move_constructible::value is true for all i."*, and so participate in overload resolution... It seems libc++ remove it from overload resolution. – Jarod42 Nov 10 '16 at 20:37
  • Looking at libc++'s code, there does appear to be a bug. Reported as https://llvm.org/bugs/show_bug.cgi?id=30979 – T.C. Nov 10 '16 at 20:54

1 Answers1

2
    auto t = std::make_tuple(std::forward<Ts>(xs)...);

This is indeed a correct way of forwarding arguments into a tuple.

The compile errors you get are caused by explicitly declaring copy_only's move constructor as deleted. Normally, if you declared a copy constructor, it'd be omitted and in move contexts the copy constructor would be chosen - as it has since C++98. But because you explicitly declared it, it does participate in the overload resolution and causes the code to be ill-formed if selected.

Here's a helpful chart courtesy of Howard Hinannt: chart

This can be solved by removing the offending line from the class definition:

struct copy_only
{
    copy_only() = default;
    //copy_only(copy_only&&) = delete;
    copy_only(const copy_only&) = default;
};

Now, as to whether your code should compile or not: as far as I can tell, it should. tuple's move constructor is defined as:

tuple(tuple&& u) = default;

Requires: is_move_constructible<Ti>::value is true for all i.

Since copy_only is not move constructible, it shouldn't be declared and shouldn't participate in overload resolution.

Community
  • 1
  • 1
krzaq
  • 16,240
  • 4
  • 46
  • 61
  • There is also the `tuple` implementation which play a role. – Jarod42 Nov 10 '16 at 20:40
  • @Jarod42 I just saw your comment. I'm not sure if deleted move constructor would mean that a type isn't move constructible or not. – krzaq Nov 10 '16 at 20:44
  • 1
    This is messy. `is_move_constructible` is basically `is_constructible` for referenceable types, which is false for `copy_only` because overload resolution selects a deleted ctor. OTOH, the `= default;` complicates things, because a defaulted move ctor is defined as deleted if, among other things, the overload resolution for moving a subobject selects a deleted ctor, yet such a defined-as-deleted defaulted move ctor is ignored by overload resolution, and if that happens then `is_move_constructible>` is true because it uses the copy ctor. – T.C. Nov 10 '16 at 21:02
  • 1
    ...and arguably in that case the requires clause on the move ctor is irrelevant because you never actually used it. Ugh. – T.C. Nov 10 '16 at 21:03
  • @T.C. then it seems my answer is at best partially correct. – krzaq Nov 10 '16 at 21:06
  • @T.C. The tuple's default move constructor does indeed have that requires clause, but it doesn't have the "This constructor shall not participate in overload resolution unless ..." incantation. Does this mean it should be ill-formed if it's used? – krzaq Nov 10 '16 at 21:21
  • The problem is that one can argue that the incantation is implicit in the core language rules for a defaulted move constructor. – T.C. Nov 10 '16 at 21:24
  • @T.C. okay, but consider [this](http://melpon.org/wandbox/permlink/1EnCBKwOiRXIdddV) case (default move constructor is correctly defined, even though it calls copy constructor for one of its members). This is all okay with the core rules, but breaks the requires clause of tuple. I have to say, this is confusing. – krzaq Nov 10 '16 at 21:40
  • @T.C. consider `test` is `tuple`. You said that the incantation is implicit in the core language rules, but in case of `test` defaulted move constructor is clearly generated. Just as it would be for `tuple`, but for `tuple` it breaks the contract. – krzaq Nov 10 '16 at 21:52
  • 1
    That's not what I'm saying. A defaulted move constructor is implicitly defined as deleted if, among other things, moving a subobject would use a constructor that's deleted. And such a constructor doesn't participate in overload resolution. Your `test` has no subobject. – T.C. Nov 10 '16 at 22:12
  • @T.C. Oh great, I forgot to insert a copy-only member and drew my conclusions basing on that. I feel so stupid now. Okay, you're obviously right. So: if I understand correctly, if one was to accept this argument about core language rules, the OP's code should compile under C++14 standard, right? – krzaq Nov 10 '16 at 22:18
  • I think so, yeah. But if it does it's just accidental - the committee couldn't care less about this kind of crazy types. – T.C. Nov 10 '16 at 22:43