Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Defaulted concept-constrained function never selected for instantiation

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:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

  • A constructor / destructor / member-function is constrained by requires, and
  • This constructor / destructor / member-function is implicitly deleted for some instantiation of T that does not satisfy the requires clause

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.

Live Example

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;
...

Live Example


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.

>Solution :

Your code is correct, and indeed was the motivating example for supporting this sort of thing (see P0848, of which I am one of the authors). Clang simply doesn’t implement this feature yet (as you can see here).

gcc and msvc both accept your code.


Note that you don’t need empty anymore, just union { T val; } is sufficient in C++20, thanks to P1331. Demo.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading