Search code examples
c++templatesconstructor-overloadingforwarding-reference

Why is the universal T&& constructor called when another constructor is more specific?


In the following snippet I have reconstructed a small failing example of what I'm working on right now. The class entity should be able to gobble up different "functor" types, hence it has an universal reference argument.

However, it seems that this template will even be called for when I clearly want to only use the copy constructor as in entity a{n}; In my final code this leads to infinite recursion. Why is this happening and how can I prevent it? I have even explicitely excluded the entity type from the overload, but to no avail...

Demo

#include <cstdio>
#include <utility>

struct functor_t
{
    void* something_;
};

struct entity
{
    entity()
    {
        functor_ = new functor_t();
    }

    entity(functor_t* functor)
        :   functor_{ functor }
    {
        printf("entity(member_t)\n");
    }

    ~entity() {
        // delete resource
        printf("~entity()\n");
        delete functor_;
    }

    template <typename T> requires (!std::same_as<std::remove_cvref<T>, entity>)
    entity(T&& other) { // <-- why you??
        printf("entity(T&&)\n"); 
        // functor_ = new functor_t(/* */);
    }

    entity(const entity& other) {
        printf("entity(const entity&)\n");
        functor_ = new functor_t(other.functor_);
    }

    entity(entity&& other) {
        printf("entity(entity&&)\n");
        functor_ = new functor_t(std::move(other.functor_));
    }

    template <typename T>
    auto operator=(T&& other) -> entity& {
        entity{std::forward<T>(other)}.swap(*this);
        return *this;
    }

    auto swap(entity& other) -> void {
        std::swap(functor_, other.functor_);
    }

    functor_t* functor_ = nullptr;
};

int main() 
{
    entity n;
    entity a{n};
    entity b;

    b = a;

    printf("--------------------\n");
}

Yields:

entity(T&&)
entity(T&&)
~entity()
--------------------
~entity()
~entity()
~entity()

Even though I don't want to call the universal constructor.


Solution

  • Why is the universal T&& constructor called when another constructor is more specific?

    The template constructor is the best candidate for overload resolution because it doesn't require any conversions. The copy constructor accepts a reference to const qualified T, and the given expression is an lvalue of non-const T which requires an implicit conversion and hence that overload is less preferred.

    I have even explicitely excluded the entity type from the overload

    The requires clause doesn't disqualify the template constructor because std::same_as<std::remove_cvref<entity&> is not the same class as entity. You probably intended to use std::same_as<std::remove_cvref_t<entity&> instead.

    how can I prevent it?

    Fix the requires clause, as per previous paragraph.

    Providing the constructor entity(entity&) would also work, but I don't recommend that approach.