Deleted Functions vs Private Members in C++

In modern C++, developers frequently need to restrict access to specific functions, such as preventing object copying or disallowing certain type conversions. Historically, this was achieved by declaring a member function as private and leaving it undefined (or unexported), whereas C++11 introduced explicitly deleted functions using the = delete specifier. While both techniques prevent unauthorized execution, they differ fundamentally in how they interact with overload resolution, access control, compiler diagnostics, and internal class privileges.

Overload Resolution and Error Generation

The primary functional difference lies in the sequence of compiler phases: overload resolution occurs before access control checks.

When a function is declared private but left unexported or undefined:

  1. The compiler includes the private function in the candidate set during overload resolution.
  2. If it is the best match, the compiler selects it.
  3. The compiler then evaluates access control. If the call site lacks access rights, compilation halts with an access violation error.

When a function is marked = delete:

  1. The function remains a viable candidate in overload resolution.
  2. If it provides the best match, overload resolution selects it.
  3. The compiler immediately evaluates the deletion status and produces a compilation error stating that the deleted function cannot be referenced.

This distinction is crucial when preventing unwanted implicit conversions. For example, if an overloaded method accepts an int and a deleted overload accepts a double, passing a double resolves to the deleted overload and fails immediately. If the double overload were simply private, external callers would trigger an access error rather than falling back to int, but within the class or its friends, the function might still be mistakenly called and lead to a linker error.

Member and Friend Access

An unexported private member does not protect against invocation from within the class itself or from declared friend entities:

  • Private and Undefined: Member functions and friends can still legally attempt to call the private function. Because the function is intentionally left without an export or definition, the failure is deferred to the linker, yielding an "undefined reference" or unresolved external symbol error rather than a direct compilation failure.
  • Explicitly Deleted (= delete): Deletion is absolute. Neither member functions nor friends can call an explicitly deleted function. Any attempt to invoke it results in an immediate compile-time error, ensuring full consistency across the entire codebase.

Diagnostic Clarity and Intent

Compiler diagnostics differ substantially between the two mechanisms:

  • Using = delete communicates explicit design intent to the compiler. Diagnostic messages explicitly report that the code attempted to invoke a deleted function, often highlighting the exact line where = delete was declared.
  • Private, unexported functions report access violations (e.g., "function is private within this context") for external callers, or missing symbols at link time for internal callers, obscuring the design intent that the operation is fundamentally invalid.

Scope of Application

Deleted functions possess broader functional utility than access specifiers:

  • Non-Member Functions: Free functions and namespace-scope functions can be explicitly deleted (e.g., to prevent specific argument type conversions), whereas private access is restricted exclusively to class members.
  • Function Template Specialization: Specific template instantiations can be deleted to disable problematic types while keeping the primary template enabled for all other types. Private access cannot selectively disable template specializations in this manner.

Impact on Type Traits

Type traits inspect the validity of operations at compile time:

  • Classes with explicitly deleted special member functions (such as copy constructors or copy assignment operators) correctly update type traits such as std::is_copy_constructible and std::is_copy_assignable to evaluate to false.
  • Private special member functions in older C++ standards often complicated SFINAE contexts and introspection traits, whereas deleted definitions integrate natively with modern template metaprogramming and concept constraints.