6

I'm writing a class where I have a templated constructor and copy constructor. Every time I want to call copy constructor with non const object, templated constructor gets chosen. How can I force compiler to choose copy constructor?

Here is the mcve:

#include <iostream>

struct foo
{
    foo()
    {
        std::cout << "def constructor is invoked\n";
    }

    foo(const foo& other)
    {
        std::cout << "copy constructor is invoked\n";
    }

    template <typename T>
    foo(T&& value)
    {
        std::cout << "templated constructor is invoked\n";
    }
};

int main()
{
    foo first;
    foo second(first);
}

Deleting a function is not what I want.

Incomputable
  • 2,188
  • 1
  • 20
  • 40
  • Shouldn't casting the argument to a `const &foo` when *calling* the ctor do he job? The ctor is for const args, so provide one. – Peter - Reinstate Monica Sep 10 '16 at 09:21
  • @PeterA.Schneider, I'm writing `std::variant`. I don't think people will like casting. I want to keep user side clean – Incomputable Sep 10 '16 at 09:23
  • 1
    One way to avoid all these shenanigans is to provide a dummy first parameter for the forwarding constructor, so there is no possibility of confusion – M.M Sep 10 '16 at 11:34
  • 1
    It might help to think more clearly about what you want. Do you want to ban the template constructor for all `T` that is anything like `foo`? (`foo&&`, `foo&`, `const foo&`, `volatile foo&`, ...). This should be easy enough with a little `enable_if`. Or merely ban the template constructor in the particular cases where there is a viable non-template constructor? (That latter isn't possible, I think) – Aaron McDaid Sep 10 '16 at 11:35
  • @M.M, `std::variant` supports templated constructor without dummy first parameter. I want to write the implementation which conforms standard as strictly as possible. – Incomputable Sep 10 '16 at 17:03
  • @AaronMcDaid, you got it exactly right. – Incomputable Sep 10 '16 at 17:04
  • You have to provide all the special functions by yourself. Otherwise you should to make the compiler to distinct templated version of c-tors and assignment operators via SFINAE (`std::enable_if< std::is_same< std::decay_t< T >, variant >::value >`). Also you may use `std::as_const()`-function (*C++17*, *libc++*) to cast lvalue reference to `const` one. But my first assertion is essential, I sure. – Tomilov Anatoliy Sep 13 '16 at 19:06
  • Does this answer your question? [c++11 constructor with variadic universal references and copy constructor](https://stackoverflow.com/questions/32287235/c11-constructor-with-variadic-universal-references-and-copy-constructor) – xskxzr Feb 20 '20 at 02:33

2 Answers2

9

Add another constructor:

foo(foo& other) : foo( const_cast<const foo&>(other))  // for non-const lvalues
{
}

The first object in your example code is a non-const lvalue, therefore the compiler prefers foo(foo&) over foo(const &). The former is provided by the template (with T=foo&) and therefore is selected.

This solution involves providing a (non-template) constructor for foo(foo&) which then delegates construction to the copy constructor by casting it to a reference-to-const

Update, I've just realised that a foo rvalue will be taken by the template also. There are a number of options here, but I guess the simplest is to also add a delegate for foo(foo&&), similar to the one above

foo(foo&& other) : foo( const_cast<const foo&>(other))  // for rvalues
{
}
Aaron McDaid
  • 26,501
  • 9
  • 66
  • 88
  • I have a specialization for rvalue ref. Is there any other way? I believe that `static_cast` is ok because making object more const is legitimate. I already have lots of casts around, but would like to see if there is any other way. – Incomputable Sep 10 '16 at 09:08
  • `static_cast` is definitely OK, but I would be worried that I would break the code in future if I change type; for example by casting from base type to derived type. That's why I'm using `const_cast`; just to make clear to myself, and to the compiler, that only the const-ness should change – Aaron McDaid Sep 10 '16 at 09:13
7

The problem is that first is mutable, so a reference to it is a foo& which binds to the universal reference T&& more readily than const foo&.

Presumably, you intended that T was any non-foo class?

In which case a little bit of enable_if chicanery expresses intent to the compiler without having to write a load of spurious overloads.

#include <iostream>

struct foo
{
    foo()
    {
        std::cout << "def constructor is invoked\n";
    }

    foo(const foo& other)
    {
        std::cout << "copy constructor is invoked\n";
    }

    template <typename T, std::enable_if_t<not std::is_base_of<foo, std::decay_t<T>>::value>* = nullptr>
    foo(T&& value)
    {
        std::cout << "templated constructor is invoked\n";
    }

};

int main()
{
    foo first;
    foo second(first);
    foo(6);
}

expected output:

def constructor is invoked
copy constructor is invoked
templated constructor is invoked
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142