c++stdany

std::any for move-only template where static_assert inside copy-ctor equals compile error, but why?


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?


Solution

  • 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