3

I wan't to disable user declared destructor using SFINAE in union-like class as do it usual for constructors in classes:

#include <type_traits>
#include <cstdlib>

template< typename A, typename B >
struct U
{
    constexpr U() = default;
    constexpr U(A _a) : a(_a), s{false} {}
    constexpr U(B _b) : b(_b), s{true}  {}

    union
    {
        A a;
        B b;
    };

    bool s;

    template< typename = std::enable_if_t< !(std::is_trivially_destructible< A >{} && std::is_trivially_destructible< B >{}) >, // disable if A and B is trivially destructible
              bool is_noexcept = (noexcept(std::declval< A >().~A()) && noexcept(std::declval< B >().~B())) > // disable if A or B is not destructible
    ~U() noexcept(is_noexcept)
    {
        if (s) {
            b.~B();
        } else {
            a.~A();
        }
    }  
};

int main()
{ 
    struct A {};
    struct B {};
    U< A, B > u;
    static_assert(std::is_literal_type< A >{});
    static_assert(std::is_literal_type< B >{}); // =>
    static_assert(std::is_literal_type< U< A, B > >{});
    return EXIT_SUCCESS;
}

but got an error:

main.cpp:24:5: error: destructor cannot be declared as a template
    ~U() noexcept(is_noexcept)
    ^
1 error generated.
Failure!

Is there theoretical reason for this restriction in C++? Or is it just a "legacy"?

Jarod42
  • 203,559
  • 14
  • 181
  • 302
Tomilov Anatoliy
  • 15,657
  • 10
  • 64
  • 169
  • @DavidHaim default constructor also can't get parameter, but it can be template. – Tomilov Anatoliy Nov 23 '15 at 09:57
  • Destructor has to be generated, default constructor don't (class may have other constructor). – Jarod42 Nov 23 '15 at 10:01
  • see my answer please – David Haim Nov 23 '15 at 10:01
  • What nobody mentioned yet: If the destructor is called automatically because some object goes out of scope, what template type should be used? ... Right. The same reason explains why it can't have normal function parameters. – deviantfan Nov 23 '15 at 10:05
  • @deviantfan Whole (imanginary destructor seclection inclusive) class template instantiated at point of construction. Scope is irrelevant. – Tomilov Anatoliy Nov 23 '15 at 10:08
  • @Orient I'm not sure if we understand each other. Every constructed object gets destructed again at some point of time, either by leaving some scope or by getting explicitely `delete`d if allocated with `new` (and some other stuff). No destructing possibility has a place to put templae types and parameters to. – deviantfan Nov 23 '15 at 10:11
  • Btw., about your noexcept, something is very wrong with it. While you're not actually throing, the conditional inclusion suggests you might, and that's wrong. A destructor is not allowed to throw exceptions. – deviantfan Nov 23 '15 at 10:13
  • You can call in your destructor some member function template - so your desired behavior is easy to achieve that way... – PiotrNycz Nov 23 '15 at 10:13
  • @deviantfan A destructor is allowed to throw exceptions. – Tomilov Anatoliy Nov 23 '15 at 10:16
  • @PiotrNycz I want to make the compiler to generate default implementation of destructor (or to leave it trivial) conditionally. I want to make my *container* (for `A` and `B`) class to be a literal type conditionally. – Tomilov Anatoliy Nov 23 '15 at 10:17
  • @Orient `A destructor is allowed to throw exceptions.` No it's not (partially wrong, partially UB). Please read the standard. Any destructor is automatically (implicitly) noexcept. And throwing out of a noexcept function usually terminates the program instead of triggering some `catch` – deviantfan Nov 23 '15 at 10:19
  • @deviantfan I think the assertion `Any destructor is automatically (implicitly) noexcept.` is definitely wrong. No UB there too. It is bad practice, not any more. – Tomilov Anatoliy Nov 23 '15 at 10:26
  • @Orient Why don't you search it in Google, instead of stating your opinion ("I think...")? – deviantfan Nov 23 '15 at 10:27
  • @deviantfan OK, I'll do instantly. – Tomilov Anatoliy Nov 23 '15 at 10:27
  • @deviantfan: Throwing exception inside destructor may produce cases where 2 active exceptions were simultaneous alive. It is that case which is UB. – Jarod42 Nov 23 '15 at 10:31
  • @deviantfan You are right about implicit `noexcept`-ness. – Tomilov Anatoliy Nov 23 '15 at 10:32
  • This is also doable - just posted by Jarod42 - you can also check my version: http://ideone.com/OE987I – PiotrNycz Nov 23 '15 at 11:49

2 Answers2

12

Any class, U, can have one and only one destructor, which is declared within that class with the name ~U() and accepts exactly no parameters.

A template function specifies a family of functions, where a "family" describes a set containing one or more, with no upper limit on the number of members.

The two concepts are mutually exclusive. A class cannot simultaneously have exactly one destructor and a family of destructors.

Peter
  • 35,646
  • 4
  • 32
  • 74
  • Using SFINAE I can make "a family of functions" to provide the only viable function in general case. – Tomilov Anatoliy Nov 23 '15 at 10:10
  • I can't see contradiction: at first glance there are no insurmountable obstacles to introduce possibility of selection of destructors (or to make the compiler to provide default one conditionally). – Tomilov Anatoliy Nov 23 '15 at 10:12
  • 1
    That is because you don't understand the difference between a template definition and a template instantiation. A template is a specification that permits multiple instantiations, depending on the parameters used. If exactly one instantiation of a function is permitted, that function cannot be a template. SFINAE only comes into play when a valid template is subsequently instantiated. The error message you are seeing occurs when checking the template is valid. If the template itself is invalid, the code will be rejected before any attempt to instantiate the template. – Peter Nov 23 '15 at 10:14
  • Last sentence make a big sense. Really so. Maybe having Concepts I'll obtain desired possibility. – Tomilov Anatoliy Nov 23 '15 at 10:20
  • Without knowing what the "desired possibility" is, I can only guess. But I suspect what you probably need to do is have (partial) specialisations of your templated struct `U`. Each specialisation of `U` will have its own destructor. But the destructor itself will not be templated. – Peter Nov 23 '15 at 10:36
  • I want to make destructor defaulted conditionally. – Tomilov Anatoliy Nov 23 '15 at 10:42
  • Then you want to look at how you specialise struct `U` - since each specialisation is permitted only one destructor. – Peter Nov 23 '15 at 10:54
4

As a work around, you may add layer and specialization, something like:

template <typename A, typename B,
          bool trivially_destructible = std::is_trivially_destructible<A>{}
                                    && std::is_trivially_destructible<B>{},
          bool is_no_except = noexcept(std::declval<A>().~A())
                        && noexcept(std::declval<B>().~B())>
struct UdestructorImpl;

template <typename A, typename B, bool is_no_except>
struct UdestructorImpl<A, B, true, is_no_except>
{
    ~UdestructorImpl() noexcept(is_no_except) = default;
}

template <typename A, typename B, bool is_no_except>
struct UdestructorImpl<A, B, false, is_no_except>
{
    ~UdestructorImpl() noexcept(is_no_except) = delete;
}

template< typename A, typename B > struct U : UdestructorImpl<A, B>
{
    // Implementation
};
Jarod42
  • 203,559
  • 14
  • 181
  • 302