3

I'm trying to use std::is_copy_assignable_v to detect whether a class has Class& operator=(const Class&):

#include <iostream>
#include <memory>
#include <unordered_map>
#include <map>

int main() {
    // All of them are true, Why???
    std::cout << std::is_copy_assignable_v<std::unordered_map<int, int>> << std::endl;
    std::cout << std::is_copy_assignable_v<std::unordered_map<int, std::unique_ptr<int>>> << std::endl;
    std::cout << std::is_copy_assignable_v<std::unordered_map<std::unique_ptr<int>, std::unique_ptr<int>>> << std::endl;
    std::cout << std::is_copy_assignable_v<std::unordered_map<std::unique_ptr<int>, int>> << std::endl;

    return 0;
}

std::unique_ptr will delete it's copy assignment function and so will std::unordered_map, isn't it?

And the compile say it has copy assignment, but when I try:

std::unordered_map<std::unique_ptr<int>, int> a;
std::unordered_map<std::unique_ptr<int>, int> b;
a = b;

It can't compile.

This doesn't seem to be a compiler problem either(try it on godbolt).

Aamir
  • 1,974
  • 1
  • 14
  • 18
VisualGMQ
  • 49
  • 5
  • Side note : you can also use `static_assert( std::is_copy_assingable_v...)` – Pepijn Kramer Jul 18 '23 at 08:09
  • And I cannot find why this should be so, it looks like this scenario was missed. The documentation on cppreference doesn't mention anything about constraints (like copyable) on type T (which I think it should have). Also on godbolt : all three compilers show the same behavior so it doesn't seem to be a compiler specific bug. – Pepijn Kramer Jul 18 '23 at 08:17
  • you could try it https://godbolt.org/z/rbYr7vefa – 463035818_is_not_an_ai Jul 18 '23 at 08:20
  • @PepijnKramer Yeah, I've tried everything you've tried.Maybe I should add these result to the post – VisualGMQ Jul 18 '23 at 08:22
  • I believe the problem is that `std::is_copy_assignable_v` tests if such function exists/declared but doesn't test if it compiles nor instantiates it in any way - nor C++ provides ways to do so. And it is defect in the template definition of `std::unordered_map` that it doesn't prevent copy construction in the function declaration but rather fails during function instantiation. – ALX23z Jul 18 '23 at 08:26

2 Answers2

2

Here is a simplified demonstration.

template <typename A>
struct testme {
    void operator=(const testme&) {
        static_assert(sizeof(A) == 11111);
    }
};

int main()
{
    std::cout << std::is_copy_assignable_v<testme<int>>;
}

The problem here is that is_copy_assignable_v (or any SFINAE-based check really) does not do deep template instantiation. The standardese for this is "immediate context". In our case is_copy_assignable_v in effect checks that the expression

declval<testme<int>&>() = declval<const testme<int>&>

is valid (the assignment operator is declared and not deleted). But it doesn't check testme<int>::operator= body because it is not in the immediate context of the parameter being substituted.

Without changing this fundamental property of the language, the only way to make sure that things like std::unordered_map<std::unique_ptr<int>, int> a; are not classified as copy assignable is to require that every single template explicitly SFINAEs away its assignment operator. In case of std::unordered_map, the copy assignment operator declaration, instead of looking like this

auto operator=(const unordered_map& other) -> unordered_map&

should be something like this

auto operator=(const unordered_map& other) -> 
        enable_if_t<is_copy_constructible_v<_Value_type>,
                    unordered_map&>

Add here the copy constructor, move assignment, and move constructor, and multiply by the number of templates in the library, and it's a whole lot of work for the library implementors. And it still guarantees nothing. The users should do the same with their templates, otherwise unordered_map<int, testme<int>> would be still classified as copy assignable.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
1

As an example let me show you the nonsense that C++ permits

#include <memory>

template<typename T>
class foo
{
    T a;
    public:
    foo() = default;
    foo(foo const& other) : a(other.a)
    {
        
    }
};

template<typename T>
class goo
{
    T a;
    public:
    goo() = default;
    goo(goo const& other) = default;
};

static_assert(std::is_copy_constructible_v<foo<std::unique_ptr<int> > >); // tests if foo(foo const&) is declared - it is
static_assert(!std::is_copy_constructible_v<goo<std::unique_ptr<int> > >); // tests if goo(goo const&) declaration does not exist - it doesn't 

int main()
{
    foo<std::unique_ptr<int> > b; // compiles despite faulty foo(foo const&) as it is never instantiated
    // foo<std::unique_ptr<int> > c(b); // will fail to compile

    return 0;
}

Basically, compiler views std::unordered_map<int, std::unique_ptr<int>> to be copy assignable but assignment fails to compile because implementation has bugs. Yet as long as it is never instantiated there won't be any compilation error reports.

I think you can file a defect in STL.

ALX23z
  • 4,456
  • 1
  • 11
  • 18
  • The defect should be filed for libstdc++ (gcc) and libc++ (llvm). – Marc Glisse Jul 18 '23 at 09:52
  • not sure if its a defect or intended. [cppref](https://en.cppreference.com/w/cpp/types/is_assignable) notes : "This trait does not check anything outside the immediate context of the assignment expression: if the use of T or U would trigger template specializations, generation of implicitly-defined special member functions etc, and those have errors, the actual assignment may not compile even if std::is_assignable::value compiles and evaluates to true. " – 463035818_is_not_an_ai Jul 18 '23 at 11:11
  • @MarcGlisse the defect I believe is in standart. Not the compiler/library imlementations. At least according to specification I saw on cppref – ALX23z Jul 18 '23 at 12:07
  • 1
    @463035818_is_not_an_ai I don't think something can changed about the shallow checking mechanism of the type traits. But the issue is with `unordered_map` as it doesn't properly specify when it is copyable. – ALX23z Jul 18 '23 at 12:09
  • Then it should be reported to the standard committee (wg21). STL doesn't really mean anything. Note that even if the problem is in the standard, implementations have some margin to get a more useful behavior without contradicting the standard (QoI). – Marc Glisse Jul 18 '23 at 21:17