Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

parameter pack templated constructor deletes copy assignment

Trying to understand why having a parameter pack templated constructor for a class apparently causes both the copy constructor and the copy assignment operator to be optimized out. (Actually I can see how the compiler wouldn't be able to discern the copy constructor signature as different from the templated constructor, but seems like it should be obvious when the copy assignment operator is used)

Example code"

#include <array>
#include <iostream>


struct A
{
    std::array<int,3> a;

    template<typename ... Args>
    A(Args&& ... args)
        :   a{args ...}
    {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
    
    A(const A& other)
        :   a{other.a}
    {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }

    A& operator=(const A& other)
    {
        a = other.a;
        std::cout << __PRETTY_FUNCTION__ << std::endl;
        return *this;
    }
};

int main()
{
    A a(1,2.f,3.);
    A b = a; // fails (see compiler output)
    //A c(a); // also fails (templated constructor is better match)
}

Compile output:

templated_constructror.cpp: In instantiation of ‘A::A(Args&& ...) [with Args = {A&}]’:
templated_constructror.cpp:35:8:   required from here
templated_constructror.cpp:11:15: error: cannot convert ‘A’ to ‘int’ in initialization
   11 |   : a{args ...}
like image 610
user1470475 Avatar asked Mar 06 '26 21:03

user1470475


1 Answers

A possible fix:

#include <array>
#include <iostream>
#include <type_traits>

struct A {
    std::array<int, 3> a;

    template <typename... Args,
              std::enable_if_t<sizeof...(Args) != 1, bool> = true>
    A(Args&&... args) : a{args...} {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
    template <typename Arg,
              std::enable_if_t<!std::is_same<std::decay_t<Arg>, A>::value,
                               bool> = true>
    A(Arg&& arg) : a{arg} {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }

    A(const A& other) : a{other.a} {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }

    A& operator=(const A& other) {
        a = other.a;
        std::cout << __PRETTY_FUNCTION__ << std::endl;
        return *this;
    }
};

int main() {
    A a(1, 2.f, 3.);
    A b = a;  // now OK
    A c(a); // now OK
}

It can certainly be improved and I'm not sure it is what you want to achieve. Basically first constructor catches only initialisation with 0 or 2+ arguments. The second one is used with only one argument if it's not a A. The third one is the normal copy constructor. live Demo

like image 186
Oersted Avatar answered Mar 09 '26 10:03

Oersted