While working with C++20, I encountered an oddity which I'm unsure is a compiler defect due to C++20's infancy, or whether this is correct behavior.
The problem is that a specific constrained function is either not selected correctly, or produces a compile-error -- depending entirely on the order of the definitions.
This occurs in specific circumstance:
- A constructor / destructor / member-function is constrained by
requires, and - This constructor / destructor / member-function is implicitly deleted for some instantiation of
Tthat does not satisfy therequiresclause
For example, consider this basic naive_optional<T> implementation:
template <typename T>
class naive_optional {
public:
naive_optional() : m_empty{}, m_has_value{false}{}
~naive_optional() requires(std::is_trivially_destructible_v<T>) = default;
~naive_optional() {
if (m_has_value) {
m_value.~T();
}
}
private:
struct empty {};
union {
T m_value;
empty m_empty;
};
bool m_has_value;
};
This code works fine for trivial types like naive_optional<int>, but fails to instantiate for non-trivial types such as naive_optional<std::string> due to an implicitly deleted destructor:
<source>:26:14: error: attempt to use a deleted function
auto v = naive_optional<std::string>{};
^
<source>:8:5: note: explicitly defaulted function was implicitly deleted here
~naive_optional() requires(std::is_trivially_destructible_v<T>) = default;
^
<source>:17:11: note: destructor of 'naive_optional<std::basic_string<char>>' is implicitly deleted because variant field 'm_value' has a non-trivial destructor
T m_value;
^
1 error generated.
If the order of definitions is changed so that the constrained function is below the unconstrained one, this works fine -- but instead selects the unconstrained function every time:
...
~naive_optional() { ... }
~naive_optional() requires(std::is_trivially_destructible_v<T>) = default;
...
My question is ultimately: is this behavior correct -- am I misusing requires? If this is correct behavior, is there any simple way to accomplish conditionally default-ing a constructor / destructor / function that is not, itself, a template? I was looking to avoid the "traditional" approach of inheriting base-class implementations.