sfinae away a destructor
Asked Answered
A

2

9

I am implementing something very similar to std::vector but uses array on the stack instead of memory allocation.

The d-tor calls a function that uses SFINAE.

  • If value_type is POD the function have empty body.
  • If value_type is normal class such std::string, the function have a body and destroy all the data properly.

Now, I want to be able to use this new std::vector as constexpr. However even the c-tor is declared constexpr, the code does not compiles because the class have non trivial d-tor.

Here is small part of the code:

template<typename T, std::size_t SIZE>
class SmallVector{
    constexpr SmallVector() = default;

    ~SmallVector(){
        destructAll_<value_type>();
    }

    // ...

    template<typename X>
    typename std::enable_if<std::is_trivially_destructible<X>::value == true>::type
    destructAll_() noexcept{
    }

};

Is there anything I can do to make class be constexpr if value_type is POD and keeping functionality for non POD data types.
(Not at the same time of course)

Anatolic answered 17/10, 2016 at 20:12 Comment(7)
I would instead inherit from SmallVectorImpl<T, SIZE, is_trivially_destructible<T>::value> which will have a main definition and a specialization for trivially destructible types. The only difference between the two being the destructor.Hacker
my idea is similar - one read only base class and all mutating methods in two inherited versions. Can you pls make an answer with your idea too, because I do not see how third template "component" helps. do you mean to inherit from two base classes?Anatolic
How similar to std::vector can such a type be, really? Are you rather reimplementing std::array? Orrrr is it a vector with a huge auto-storage array and lots of placement new? :)Cradling
@LightnessRacesinOrbit: I assume rewriting vector + stack-backed-pool-allocatorHereunto
@LightnessRacesinOrbit - is academical purpose. and is like array with push back and lots of placement new.Anatolic
try using if constexpr?Borrell
@Borrell Check My answer of 'if constexpr'Precipitin
S
9

until C+20

Unfortunately, there is no way to enable/disable destructor with SFINAE, nor with future concepts. That is because destructos:

  • can't be templated
  • can't have arguments
  • can't have a return type

What you can do is specialize whole class, or better yet, create a base class that contains only the construct/destruct and basic access and specialize that.

template <class T, class Enable = void>
struct X {
    ~X() {}
};

template <class T>
struct X<T, std::enable_if_t<std::is_pod<T>::value>> {
};

static_assert(std::is_trivially_destructible<X<int>>::value);
static_assert(!std::is_trivially_destructible<X<std::vector<int>>>::value);

C++ 20

As far as I can tell you can constraint a destructor and get exactly what you want in a very simple and elegant solution:

template<typename T, std::size_t SIZE>
class SmallVector{
public:
    constexpr SmallVector() = default;

    ~SmallVector() requires std::is_trivially_destructible_v<T> = default;

    ~SmallVector()
    {   
    }
};

static_assert(std::is_trivially_destructible_v<SmallVector<int, 4>>);
static_assert(!std::is_trivially_destructible_v<SmallVector<std::string, 4>>);

However this is a brand new feature and there have been some changes lately (e.g. see Disable non-templated methods with concepts) and the compiler support is still sketchy. gcc compiles this just fine, while clang is confused by the fact that there are two definitions of the destructor godbolt

Shaunshauna answered 17/10, 2016 at 22:15 Comment(1)
I had an idea to use SFINAE inside noexcept specifier of the destructor, but SFINAE does not apply there, unfortunately.Saltillo
P
-3

The example of if constexpr in destructor. (C++17 required)

template<typename Tp, typename TLock>
struct LockedPtr {
private:
    Tp   *m_ptr;
    TLock *m_lk;
    void prelock(std::mutex *mtx) { mtx->lock(); }
    void prelock(std::atomic_flag *atom) { while(atom->test_and_set(std::memory_order_acquire)); }

public:
    LockedPtr(Tp *ptr, TLock *mtx)
    : m_ptr(ptr), m_lk(mtx) {
        prelock(mtx); 
    }

    ~LockedPtr() {
        if constexpr (std::is_same_v<TLock, std::mutex>)
            ((std::mutex *)m_lk)->unlock();
        if constexpr (std::is_same_v<TLock, std::atomic_flag>)
            ((std::atomic_flag *)m_lk)->clear(std::memory_order_release);
    }
};

These code is the part of RAII locked smart pointer, to adopt to normal std::mutex and spinlock by std::atomic_flag.

  • Using function overload to match different type in constructor.
  • Match type by if constexpr and make something unconvertable to pointer in destructor.
Precipitin answered 3/7, 2021 at 12:13 Comment(4)
I think you might have posted to the wrong question?Lidia
See the example of ~LockedPtr(), to match type by if constexpr and make something unconvertable to pointerPrecipitin
I agree that OP could've used if constexpr in the destructor, but it's very confusing to post an big unrelated class just to demonstrate it.Complected
@Complected Thank you for your advise, the unnecessary code has removed.Precipitin

© 2022 - 2024 — McMap. All rights reserved.