Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prevent compiler from considering implicitly-declared copy constructor in C++03

Tags:

c++

Note that I am working in C++03, and the deleted functions of C++11 are not available to me.

I am attempting to design a non-copyable object, and prevent the compiler from considering the implicitly-declared copy constructor on that class. This is for a unit test fixture I am developing.

Consider that I have two main objects: a core library object, Root, and a derived special-case object under test, Branch. I am trying to develop a test fixture class, Fixture which handles the minutiae of setting up & talking to the core Root object. So this is a simplified illustration of what I've built so far:

(Here is an ideone link with the same code below, except I've defined my own noncopyable)

#include <boost/utility.hpp>
#include <boost/noncopyable.hpp>

class Root
{
};

class Fixture
:
    public boost::noncopyable
{
public:
    Fixture (Root& root)
    :
        mRoot (root)
    {
    }
private:
    Root& mRoot;
};

class Branch
:
    public Root,
    public Fixture
{
public:
    Branch()
    :
        Fixture (*this)
    {
    }
};

int main()
{
    Branch branch;
}

Compiling this results in:

main.cpp: In constructor ‘Branch::Branch()’:
main.cpp:30:23: error: call of overloaded ‘Fixture(Branch&)’ is ambiguous
main.cpp:30:23: note: candidates are:
main.cpp:13:5: note: Fixture::Fixture(Root&)
main.cpp:8:7: note: Fixture::Fixture(const Fixture&)

It is not possible* to prevent the C++03 compiler from implicitly declaring a copy constructor for Fixture unless I declare at least one on my own. But even with:

class Fixture
:
    public boost::noncopyable
{
public:
    Fixture (Root& root)
    :
        mRoot (root)
    {
    }
private:
    Fixture (const Fixture&);
    Fixture (Fixture&);
    Root& mRoot;
};

...the compiler will still consider these private declarations when initializing Fixture in Branch's initialization list:

Fixture (*this)

I want the compiler to simply not consider these copy constructors.

I could do this by doing a little contorting on my own:

Fixture (static_cast <Root&> (*this))

...but I'd rather not, as it's a bit smelly to my nose and non-copy-ability is the semantics of what I'm going for by deriving Fixture from boost::noncopyable.

Is there a way to prevent the compiler from considering implicitly-declared copy constructors in this case without changing code at the call-site from:

Fixture (*this)

?


  • "It is not possible..." : Standard C++03: 12.8/4, "Special member functions":

If the class definition does not explicitly declare a copy constructor, one is declared implicitly.

like image 841
John Dibling Avatar asked Jun 21 '13 15:06

John Dibling


People also ask

When copy constructor is used implicitly for what purpose?

The copy constructor is used only for initializations, and does not apply to assignments where the assignment operator is used instead. The implicit copy constructor of a class calls base copy constructors and copies its members by means appropriate to their type. If it is a class type, the copy constructor is called.

When copy constructor is implicitly called?

In C++, a Copy Constructor may be called for the following cases: 1) When an object of the class is returned by value. 2) When an object of the class is passed (to a function) by value as an argument. 3) When an object is constructed based on another object of the same class.

Which of the following declaration is implicitly created copy constructor?

Otherwise, the implicitly-declared copy constructor is T::T(T&).

Is copy constructor automatically created by compiler?

The compiler also creates a copy constructor if we don't write our own copy constructor. Unlike the default constructor, the body of the copy constructor created by the compiler is not empty, it copies all data members of the passed object to the object which is being created.


3 Answers

Your ambiguity is that *this can bind to both a Root & and a Fixture &, and both conversions are equally good (namely derived-to-base conversions).

The trick is to create an overload that's a better match. For example,

template <typename T> Fixture(T &)

is will match any lvalue exactly, and thus is a better match than an overload that requires a conversion.

However, this is too naive, since you don't actually want your Fixture to be constructible from just any­thing. Rather you want it to be constructible only from something that's derived from Root. We can disable the extraneous constructors with some SFINAE magic. First the C++11 version:

#include <type_traits>

template <typename T,
          typename = typename std::enable_if<std::is_base_of<Root, T>::value>::type>
Fixture(T & x)
: mRoot(x)
{ }

In C++03, we use Boost, and we can't use default template arguments:

#include <boost/type_traits.hpp>

template <typename T>
Fixture(T & x,
        typename boost::enable_if<boost::is_base_of<Root, T> >::type * = NULL)
: mRoot(x)
{ }

Now you are guaranteed that T is derived from Root. The overload of this templated constructor with T = Branch is an exact match, better than the copy constructor, and so it is selected as the best overload unambiguously.

like image 171
Kerrek SB Avatar answered Sep 30 '22 17:09

Kerrek SB


There is no way to prevent the existence of the copy constructor signature, not in C++98, and not in C++11 either. = delete does not remove something from the overload set either, it only fails if it is selected.

I have no better idea than to insert the explicit cast if you don't want to mess with the public interface of Fixture.

Options that do mess with the interface include passing the Root by pointer to distinguish from the reference of the copy constructor and passing a tag for the sake of overload resolution. Leave a comment if you want to know more about these.

like image 41
Sebastian Redl Avatar answered Sep 30 '22 19:09

Sebastian Redl


If you're not going to pass around Branch as an instance of Fixture, there is no need for inheriting it at all. What you basically would like to do is be able to set something up in Fixture for all instances of Root, if I'm not mistaken. So lets attack that cause, instead of bending C++. DISCLAIMER: If it's otherwise, I've no suggestions, I'm afraid.

For this problem, I'd make Branch have an instance of Fixture as it's member and overload copy-constructor of Branch to create the instance of Fixture by passing itself as the instance to Fixture's constructor and assignment operator to never copy Fixture instance. A brief example is shown below:

#include <boost/utility.hpp>
#include <boost/noncopyable.hpp>

class Root
{
};

class Fixture
:
    public boost::noncopyable
{
public:
    Fixture (Root& root)
    :
        mRoot (root)
    {
    }
private:
    Root& mRoot;
};

class Branch
:
    public Root
{
public:
    Branch()
    : mFixture(*this)
    {
    }

    Branch(const Branch& branch)
    : Root(*this)
    , mFixture(*this)
    /* other 'Branch' members to be copied */
    {
    }

    Branch& operator = (const Branch& branch)
    {
        Root::operator=(branch);
        /* copy other 'Branch' members, except 'mFixture' */
    }

    Fixture& getFixture()
    {
        return mFixture;
    }

    const Fixture& getFixture() const
    {
        return mFixture;
    }

private:
    Fixture mFixture;
};
like image 43
Vite Falcon Avatar answered Sep 30 '22 19:09

Vite Falcon