Force compiler to choose copy constructor with const T& as a parameter
Asked Answered
H

2

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.

Headmost answered 10/9, 2016 at 8:51 Comment(8)
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.Digital
@PeterA.Schneider, I'm writing std::variant. I don't think people will like casting. I want to keep user side cleanHeadmost
One way to avoid all these shenanigans is to provide a dummy first parameter for the forwarding constructor, so there is no possibility of confusionAdequate
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)Wishywashy
@M.M, std::variant supports templated constructor without dummy first parameter. I want to write the implementation which conforms standard as strictly as possible.Headmost
@AaronMcDaid, you got it exactly right.Headmost
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.Dollop
Does this answer your question? c++11 constructor with variadic universal references and copy constructorBillman
M
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
Moa answered 10/9, 2016 at 11:34 Comment(0)
W
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
{
}
Wishywashy answered 10/9, 2016 at 9:1 Comment(2)
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.Headmost
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 changeWishywashy
M
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
Moa answered 10/9, 2016 at 11:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.