BrockLee
BrockLee

Reputation: 981

Why is this assignment still allowed if the assignment operator is made private?

Given this example:

// test.cpp
class A {
public:
private:
    A& operator=(const A&);  // don't allow assignment operator
    A(const A&);  // don't allow default copy constructor
    void func();
};

template<class T>
class B {
public:
    B(A &a) : a_(a) {}
    void setA(A* a) { a_ = *a; }  // No error is raised here.
    void func();

protected:
    B& operator=(const B&) { return *this; }
    A& a_;
};

I would expect for an error to be raised around void setA(A* a) { a_ = *a; } in B since A's assignment operator is made private and A and B aren't friends, but no error get's raised when I compile this.

$ g++ -c test.cpp  // My invocation for reference

My question is why is this allowed? Is this behavior perhaps guaranteed according to the c++ standard?

I noticed that if I did not make B a templated class, I get the error as expected.

$ g++ -c test.cpp
test.cpp: In member function ‘void B::setA(A*)’:
test.cpp:11:29: error: ‘A& A::operator=(const A&)’ is private within this context
     void setA(A* a) { a_ = *a; }
                             ^
test.cpp:4:8: note: declared private here
     A& operator=(const A&);  // don't allow assignment operator
        ^~~~~~~~

This led me to believe that because I wasn't actually "using" the templated B that the compiler could simply "ignore it" and optimize it out. I find this difficult to believe though since I'm not compiling with optimizations, and I still can't reproduce the error when I do use templated B.

// Appending this to test.cpp still doesn't result in an error.
void func(A &alloc) {
  B<int> b(alloc);
  b.func();
}

I can also confirm that for normal methods I get the error I expect. Changing void func(); in B to void func() { a_.func(); } results in:

test.cpp:14:22: error: 'func' is a private member of 'A'
    void func() { a_.func(); }
                     ^
test.cpp:6:10: note: declared private here
    void func();
         ^

I've confirmed all this behavior with clang++ (v6.0.1), g++ (v7.4.0), and tip of tree clang, and against -std=c++98 to -std=c++17.

Upvotes: 1

Views: 105

Answers (2)

Lightness Races in Orbit
Lightness Races in Orbit

Reputation: 385405

My question is why is this allowed?

It isn't.

But, since you never actually instantiated the template B<T>::setA(), the code has not "finished compiling".

I find this difficult to believe though since I'm not compiling with optimizations

This has nothing to do with optimisations.

I still can't reproduce the error when I do use templated B.

You need to call setA() on it.


You might consider this to be an extension of Substitution Failure Is Not An Error; I shall call it Not Using The Broken Template Thing Is Not An Error. Or NUTBTTINAE for short. :)


See also: instantiate a std::vector<T> for some T that is not copyable or moveable, and observe that it works until you try pushing things into it.

Upvotes: 3

Igor Tandetnik
Igor Tandetnik

Reputation: 52621

Non-virtual member functions of a class template - like setA in your example - are not instantiated unless and until actually used.

Upvotes: 6

Related Questions