Search code examples
c++templatestype-traits

C++ Templated-Optional Constructors


preliminary information

I am implementing a queue. I have a Vec class that looks like this:

#include <cstddef>
#include <span>

template<typename T, size_t Extent, bool Dynamic = (Extent == std::dynamic_extent)>
class Vec {
public:
    constexpr size_t capacity(void) const;
    ~Vec();
};

template<typename T, size_t Extent>
class Vec<T, Extent, true> {
    // Dynamic version
public:
    Vec(size_t capacity);
};

template<typename T, size_t Extent>
class Vec<T, Extent, false> {
    // Statically-sized version of the class
public:
    constexpr Vec() = default;
};

In other words, Vec can be constructed with constexpr Vec() when Extent != std::dyanmic_extent. Otherwise, a size must be passed in, and the constructor is Vec(size_t capacity). This is designed to be similar to the api provided by std::span.

Question

How can I include this in a new class, Queue, such that I can have the majority of functionality be shared between the two classes, but still have specialized ctors. For example, I want to write this:

template<typename T, size_t Extent = std::dynamic_extent>
class Queue {
public:
    template</* only enable if Extent != std::dynamic_extent */>
    constexpr Queue() {}

    template</* only enable if Extent == std::dynamic_extent */>
    Queue(size_t size) : m_slots(size) {}

    // Other methods...
private:
    Vec<T, Extent> m_slots;
    // Other fields ...
};

I am aware, I could add a bool Dynamic parameter matches Vec<T>, but then I would need repeat all of the fields in each class, and provide duplicate function definitions.

I have tried a few different variants of template expressions (only templates for constexpr Queue() are shown, expressions for Queue(size_t size) are just the obvious inverses).

Attempt 1: Unnamed, default parameter, optionally invalid

template<typename = typename std::enable_if_t<Extent != std::dynamic_extent>>
constexpr Queue() {}

Attempt 2: Attempt to mimic the c++ docs on std::enable_if

template<std::enable_if_t<Extent != std::dynamic_extent, bool> = true>

Both of these attempts failed to compile, with the warning:

/usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/type_traits:2608:44: error: no type named 'type' in 'std::enable_if<false, bool>'; 'enable_if' cannot be used to disable this declaration
 2608 |     using enable_if_t = typename enable_if<_Cond, _Tp>::type;
      |                                            ^~~~~
../src/queues/spsc.hh:34:16: note: in instantiation of template type alias 'enable_if_t' requested here
   34 |         template<std::enable_if_t<Extent == std::dynamic_extent, bool> = true>
      |

How can I implement this behavior? Is there a better pattern that I'm not seeing?

Link

I've reproduced the issue, with both of my attempts, in this godbolt.


Solution

  • If you look at cppreference's documentation for std::span's constructor, you will see how it uses std::dynamic_extent in C++20 to limit some of its constructors. FYI, it's not using SFINAE in a template parameter at all, it uses the new explicit(expression) feature instead, eg:

    constexpr span() noexcept;
    
    template< class It >
    explicit(extent != std::dynamic_extent)
    constexpr span( It first, size_type count );
    
    template< class It, class End >
    explicit(extent != std::dynamic_extent)
    constexpr span( It first, End last );
    
    template< std::size_t N >
    constexpr span( std::type_identity_t<element_type> (&arr)[N] ) noexcept;
    
    template< class U, std::size_t N >
    constexpr span( std::array<U, N>& arr ) noexcept;
    
    template< class U, std::size_t N >
    constexpr span( const std::array<U, N>& arr ) noexcept;
    
    template< class R >
    explicit(extent != std::dynamic_extent)
    constexpr span( R&& range );
    
    template< class U, std::size_t N >
    explicit(extent != std::dynamic_extent && N == std::dynamic_extent)
    constexpr span( const std::span<U, N>& source ) noexcept;
    
    constexpr span( const span& other ) noexcept = default;