0

I don't see why move-only templates can not be extended by copy-ctor having a static_assert (like in the code below) in order to be used with std::any

#include <any>
#include <cassert>

namespace detail{
template<typename T=int>
struct MoveOnly
{
    MoveOnly() = default;
    MoveOnly(MoveOnly const&) {static_assert(sizeof(T)!=sizeof(T),"");}
    MoveOnly(MoveOnly &&) = default;
    MoveOnly &operator=(MoveOnly const&) {static_assert(sizeof(T)!=sizeof(T),"");}
    MoveOnly &operator=(MoveOnly &&) = default;
};
}
using MoveOnly = detail::MoveOnly<>;
static_assert(std::is_copy_constructible<MoveOnly>::value,"");

int main() {
    MoveOnly a;
    //std::any any(std::move(a)); //<- compile error
    return 0;
}

In std::any::any it says for ctor #4

This overload only participates in overload resolution if ... std::is_copy_constructible_v<std::decay_t<ValueType>> is true.

As far as I can see std::is_copy_constructible<MoveOnly>::value gives true and copy-ctor is never being called. So how is it possible that the compiler still complains about the static_assert inside copy-ctor?

ezegoing
  • 526
  • 1
  • 4
  • 18
  • Not 100% sure, but I would imagine that `std::any` is very much like `std::function` in that since it needs to go through polymorphism to be implemented, it has no choice but to require copy-constructibility of its assigned content. –  Nov 11 '19 at 21:20
  • @Frank I understand that and they check it with `std::is_copy_constructible::value` right? So I managed to get that value to true, so i don't see how they can still figure out the `static_assert` in the end without actually calling copy-ctor directly. – ezegoing Nov 11 '19 at 21:24
  • I don't understand the question. ctor #4 is going to be picked because `std::is_copy_constructible_v>` is `true`. That means you make a copy, which means your `static_assert` fires. – NathanOliver Nov 11 '19 at 21:25
  • @NathanOliver-ReinstateMonica No as far as I can see #4 is only enabled if `std::is_copy_constructible_v>` is `true`. But it still uses move-ctor if you use `std::move` – ezegoing Nov 11 '19 at 21:26
  • `std::move` doesn't move anything. All it does is cast the object to an rvalue reference. ctor #4 is always going to make a copy, that's why it requires `is_copy_constructible`. If it could move or copy, it would just require `is_move_constructible`. – NathanOliver Nov 11 '19 at 21:29
  • @NathanOliver-ReinstateMonica Change `static_assert` to `assert` in the code above and see it yourself. The move casts to rvalue ref. and therefore it uses move-ctor. – ezegoing Nov 11 '19 at 21:31
  • 1
    The function gets created anyways because it gets pointed at by a helper type's vtable. That's because the `std::any` could get copied into another `std::any` in some other translation unit. The copy constructor is not called in YOUR code, but it might be called from somewhere else. There's no way to tell ahead of time. –  Nov 11 '19 at 21:32
  • Ok. So do I understand this correct: The copy-ctor gets created and static_assert is being fired because a simple pointer is pointing to the copy-ctor? – ezegoing Nov 11 '19 at 21:39

1 Answers1

1

Consider the following case:

foo.h:

void foo(const std::any& v);

foo.cpp:

void foo(const std::any& v) {
  std::any tmp = v;
}

main.cpp:

#include "foo.h"

int main() {
    MoveOnly a;
    std::any any(std::move(a));

    foo(any);

    return 0;
}

Inside of main.cpp, there is no way of knowing whether foo() will make a copy of v or not, so we have no choice, there has to be a copy-constructor available just in case it might need to be invoked.

edit: To address something mentioned in the comments:

Ok. So do I understand this correct: The copy-ctor gets created and static_assert is being fired because a simple pointer is pointing to the copy-ctor?

In essence, yes. As soon as a pointer refers to a function, that function has to exist, and bringing that function into existence will trigger code-gen for it, including evaluating any static_assert() within.

The only thing that's a little off in your understanding is that the pointer doesn't exist just for the heck of it. It's there because there's a real possibility that it might be used to invoke the pointed function. Maybe it will get optimized away during LTO, but that's too late; code-gen is finished by then