Repository: Qiskit/qiskit

Issue:
When `num_ctrl_qubits=0`, creating controlled gates will produce an `AttributeError`
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues -->

### Information

- **Qiskit Terra version**: 0.14.1
- **Python version**: 3.8
- **Operating system**: both Windows and Linux

### What is the current behavior?

When `num_ctrl_qubits=0`, creating controlled gates will produce an `AttributeError`.

### Steps to reproduce the problem

```
>>> from qiskit.circuit.library import ZGate
>>> ZGate().control(0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.8/dist-packages/qiskit/circuit/library/standard_gates/z.py", line 100, in control
    return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, ctrl_state=ctrl_state)
  File "/usr/local/lib/python3.8/dist-packages/qiskit/circuit/gate.py", line 132, in control
    return add_control(self, num_ctrl_qubits, label, ctrl_state)
  File "/usr/local/lib/python3.8/dist-packages/qiskit/circuit/add_control.py", line 70, in add_control
    cgate.base_gate.label = operation.label
AttributeError: 'ZGate' object has no attribute 'base_gate'
```

### What is the expected behavior?

Create a gate without control bits successfully.

### Suggested solutions

This bug happens because the `control` function in`qiskit/circuit/add_control.py` [directly returns `operation` (the first argument) when `num_ctrl_qubits=0`](https://github.com/Qiskit/qiskit-terra/blob/4f804108bba528aa95e46838235754778e0cb68c/qiskit/circuit/add_control.py#L106), but the `add_control` function [expects that the return value from the `control` function has the `base_gate` attribute](https://github.com/Qiskit/qiskit-terra/blob/4f804108bba528aa95e46838235754778e0cb68c/qiskit/circuit/add_control.py#L70).

Either the `control` function should always return a `Gate` object with the `base_gate` attribute, or the `add_control` function should not assume that the `base_gate` attribute always exists.


Discussion:
Another option would be to raise an exception if num_ctrl_qubits=0. Then whenever `control` is called a `ControlledGate` is always returned. Letting num_ctrl_qubits=0 doesn't seem necessary to support since one could just use the original gate.
Indeed a `ControlledGate` with 0 control bits does not seem to be necessary to be supported. However having such support would be a little bit more user-friendly. I discovered this bug when I try to implement "multiply amplitude by -1 when all `n` qubits are `|1>`" for arbitrary `n` with the following code: 

```python
circuit.append(ZGate().control(n - 1), range(n))
```

The code fails when `n=1`, so I have to deal with this case separately.

Resolving patch:
diff --git a/qiskit/circuit/add_control.py b/qiskit/circuit/add_control.py
--- a/qiskit/circuit/add_control.py
+++ b/qiskit/circuit/add_control.py
@@ -102,12 +102,6 @@ def control(operation: Union[Gate, ControlledGate],
     # pylint: disable=unused-import
     import qiskit.circuit.library.standard_gates.multi_control_rotation_gates
 
-    # check args
-    if num_ctrl_qubits == 0:
-        return operation
-    elif num_ctrl_qubits < 0:
-        raise CircuitError('number of control qubits must be positive integer')
-
     q_control = QuantumRegister(num_ctrl_qubits, name='control')
     q_target = QuantumRegister(operation.num_qubits, name='target')
     q_ancillae = None  # TODO: add
diff --git a/qiskit/circuit/controlledgate.py b/qiskit/circuit/controlledgate.py
--- a/qiskit/circuit/controlledgate.py
+++ b/qiskit/circuit/controlledgate.py
@@ -85,10 +85,8 @@ def __init__(self, name: str, num_qubits: int, params: List,
            qc2.draw()
         """
         super().__init__(name, num_qubits, params, label=label)
-        if num_ctrl_qubits < num_qubits:
-            self.num_ctrl_qubits = num_ctrl_qubits
-        else:
-            raise CircuitError('number of control qubits must be less than the number of qubits')
+        self._num_ctrl_qubits = 1
+        self.num_ctrl_qubits = num_ctrl_qubits
         self.base_gate = None
         if definition:
             self.definition = definition
@@ -132,6 +130,31 @@ def definition(self, excited_def: List):
         """Set controlled gate definition with closed controls."""
         super(Gate, self.__class__).definition.fset(self, excited_def)
 
+    @property
+    def num_ctrl_qubits(self):
+        """Get number of control qubits.
+
+        Returns:
+            int: The number of control qubits for the gate.
+        """
+        return self._num_ctrl_qubits
+
+    @num_ctrl_qubits.setter
+    def num_ctrl_qubits(self, num_ctrl_qubits):
+        """Set the number of control qubits.
+
+        Args:
+            num_ctrl_qubits (int): The number of control qubits in [1, num_qubits-1].
+
+        Raises:
+            CircuitError: num_ctrl_qubits is not an integer in [1, num_qubits - 1].
+        """
+        if (num_ctrl_qubits == int(num_ctrl_qubits) and
+                1 <= num_ctrl_qubits < self.num_qubits):
+            self._num_ctrl_qubits = num_ctrl_qubits
+        else:
+            raise CircuitError('The number of control qubits must be in [1, num_qubits-1]')
+
     @property
     def ctrl_state(self) -> int:
         """Return the control state of the gate as a decimal integer."""
diff --git a/qiskit/circuit/library/standard_gates/x.py b/qiskit/circuit/library/standard_gates/x.py
--- a/qiskit/circuit/library/standard_gates/x.py
+++ b/qiskit/circuit/library/standard_gates/x.py
@@ -739,7 +739,7 @@ class MCXGate(ControlledGate):
     def __new__(cls, num_ctrl_qubits=None, label=None, ctrl_state=None):
         """Create a new MCX instance.
 
-        Depending on the number of controls, this creates an explicit X, CX, CCX, C3X or C4X
+        Depending on the number of controls, this creates an explicit CX, CCX, C3X or C4X
         instance or a generic MCX gate.
         """
         # these gates will always be implemented for all modes of the MCX if the number of control
@@ -748,8 +748,6 @@ def __new__(cls, num_ctrl_qubits=None, label=None, ctrl_state=None):
             1: CXGate,
             2: CCXGate
         }
-        if num_ctrl_qubits == 0:
-            return XGate(label=label)
         if num_ctrl_qubits in explicit.keys():
             gate_class = explicit[num_ctrl_qubits]
             gate = gate_class.__new__(gate_class, label=label, ctrl_state=ctrl_state)


Test patch:
diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py
--- a/test/python/circuit/test_controlled_gate.py
+++ b/test/python/circuit/test_controlled_gate.py
@@ -541,14 +541,14 @@ def test_multi_controlled_y_rotation_matrix_basic_mode(self, num_controls, use_b
             with self.subTest(msg='control state = {}'.format(ctrl_state)):
                 self.assertTrue(matrix_equal(simulated, expected))
 
-    @data(0, 1, 2)
+    @data(1, 2)
     def test_mcx_gates_yield_explicit_gates(self, num_ctrl_qubits):
         """Test the creating a MCX gate yields the explicit definition if we know it."""
         cls = MCXGate(num_ctrl_qubits).__class__
-        explicit = {0: XGate, 1: CXGate, 2: CCXGate}
+        explicit = {1: CXGate, 2: CCXGate}
         self.assertEqual(cls, explicit[num_ctrl_qubits])
 
-    @data(0, 3, 4, 5, 8)
+    @data(3, 4, 5, 8)
     def test_mcx_gates(self, num_ctrl_qubits):
         """Test the mcx gates."""
         backend = BasicAer.get_backend('statevector_simulator')
@@ -860,6 +860,16 @@ def test_open_controlled_gate_raises(self):
         with self.assertRaises(CircuitError):
             base_gate.control(num_ctrl_qubits, ctrl_state='201')
 
+    @data(-1, 0, 1.4, '1', 4, 10)
+    def test_improper_num_ctrl_qubits(self, num_ctrl_qubits):
+        """
+        Test improperly specified num_ctrl_qubits.
+        """
+        num_qubits = 4
+        with self.assertRaises(CircuitError):
+            ControlledGate(name='cgate', num_qubits=num_qubits,
+                           params=[], num_ctrl_qubits=num_ctrl_qubits)
+
     def test_open_controlled_equality(self):
         """
         Test open controlled gates are equal if their base gates and control states are equal.