Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SFINAE does not work for copy constructors

I have a template class and I would like to have two copy ctors. One for trivial types, and another for non trivial types. The following code works (with one copy ctor):

template <typename T>
struct  MyStruct
{
    MyStruct()
    {}
    
    MyStruct(const MyStruct& o)
    {
        std::cout << "copy ";
        foo(o);
    }
    
    template <typename U = T, typename std::enable_if_t<!std::is_trivial<U>::value, int> =0>
    void foo(const MyStruct& o)
    {
        std::cout << "Non trivial" << std::endl;
    }
    
    template <typename U = T, typename std::enable_if_t<std::is_trivial<U>::value, int> =0>
    void foo(const MyStruct& o)
    {
        std::cout << "Trivial" << std::endl;
    }
    
    MyStruct(MyStruct&& o)
    {
        std::cout << "Move" << std::endl;
    }
};

struct MyType
{
    MyType(int i){}
};

int main()
{
    MyStruct<int> my1;
    MyStruct<int> my2(my1);
    
    MyStruct<MyType> mytype1;
    MyStruct<MyType> mytype2(mytype1);
}

// prints
// Copy Trivial
// Copy Non trivial

When I try to do the same with two copy ctors, then it seems none of them was generated (both was SFINAEd away)

struct  MyStruct
{
    MyStruct()
    {}
    
    template <typename U = T, typename std::enable_if_t<!std::is_trivial<U>::value, int> =0>
    MyStruct(const MyStruct& o)
    {
        std::cout << "Non trivial" << std::endl;
    }
    
    template <typename U = T, typename std::enable_if_t<std::is_trivial<U>::value, int> = 0>
    MyStruct(const MyStruct& o)
    {
        std::cout << "Trivial" << std::endl;
    }

    MyStruct(MyStruct&& o)
    {
        std::cout << "Move" << std::endl;
    }
};

The code does not compile:

main.cpp: In function ‘int main()’:
main.cpp:36:26: error: use of deleted function ‘constexpr MyStruct::MyStruct(const MyStruct&)’
     MyStruct<int> my2(my1);
                          ^
main.cpp:5:9: note: ‘constexpr MyStruct::MyStruct(const MyStruct&)’ is implicitly declared as deleted because ‘MyStruct’ declares a move constructor or move assignment operator
 struct  MyStruct
         ^~~~~~~~
main.cpp:39:37: error: use of deleted function ‘constexpr MyStruct::MyStruct(const MyStruct&)’
     MyStruct<MyType> mytype2(mytype1);
                                     ^
main.cpp:5:9: note: ‘constexpr MyStruct::MyStruct(const MyStruct&)’ is implicitly declared as deleted because ‘MyStruct’ declares a move constructor or move assignment operator
 struct  MyStruct
         ^~~~~~~~

I tried to google for it, and I think SFINAE should work with copy constructors as well. Please do not suggest using if constexpt or requires concept, this question is about SFINAE. Thanks for any help!

like image 502
thamas Avatar asked Mar 21 '21 21:03

thamas


1 Answers

Unfortunately, a template constructor can't be a default, copy or move constructor.

The signature of a copy constructor for MyStruct is as follows

MyStruct (MyStruct const &);

This function, in your second example, is implicitly deleted because you've written an explicit move constructor.

Your template constructor, with the following signature,

template <typename, int>
MyStruct (MyStruct const &);

is (are) a valid constructor but, given is a template one, the (unfortunately deleted) real copy constructor is preferred.

Possible solution: add a real copy constructor that call (delegating constructor) the template constructor, through an additional argument.

I mean

template <typename T>
struct  MyStruct
{
    MyStruct()
    {}

    // delegating real copy constructor added
    MyStruct(const MyStruct & ms0) : MyStruct{ms0, 0}
     { }
    
    template <typename U = T,
              std::enable_if_t<!std::is_trivial<U>::value, int> = 0>
    MyStruct(const MyStruct& o, int = 0) // <-- added an int argument
     { std::cout << "Non trivial" << std::endl; }
    
    template <typename U = T,
              std::enable_if_t<std::is_trivial<U>::value, int> = 0>
    MyStruct(const MyStruct& o, int = 0) // <-- added and int argument
     { std::cout << "Trivial" << std::endl; }

    MyStruct(MyStruct&& o)
     { std::cout << "Move" << std::endl; }
};
like image 112
max66 Avatar answered Oct 14 '22 08:10

max66