Search code examples
c++sfinaeenable-if

Disabling a constructor entirely using `std::enable_if`


I have a template type which is parameterized with a certain pointer type. (like an iterator). I want this type to be implicitly castable to a version of itself with a const qualifier (e.g. thing<const int*>(const thing<int*>&). I want this constructor to be disabled when the pointer is already const as it clashes with the default copy constructor. Currently I have something similar to this snippet of code:

#include <type_traits>

template <typename Ptr>
struct traits {
    using ptr_type = Ptr;
    static constexpr bool is_const = std::is_const_v<std::remove_pointer_t<ptr_type>>;
    template <typename _Ptr> using rebind = traits<_Ptr>;
};

template <typename Traits>
struct thing {
    using ptr_type = typename Traits::ptr_type;
    using non_const_ptr_type = std::add_pointer_t<std::remove_const_t<std::remove_pointer_t<ptr_type>>>;
    using non_const_traits_type = typename Traits::template rebind<non_const_ptr_type>;

    thing(ptr_type p = nullptr) : ptr_(p) {}
    thing(const thing&) = default;

    template <typename = std::enable_if_t<Traits::is_const>>
    thing(const thing<non_const_traits_type>& other) :
        ptr_(const_cast<ptr_type>(other.ptr_)) {}

    ptr_type ptr_;

    template <typename> friend struct thing;
};

int main() {
    thing<traits<      int*>> t;
    thing<traits<const int*>> j(t);
}

The thing type gets its parameter from a traits type because this more accurately represents my real code. I tried to disable the constructor using std::enable_if but for some reason the compiler keeps complaining about the enable_if on a non-const thing.

error: no type named 'type' in 'struct std::enable_if<false, void>'
    using enable_if_t = typename enable_if<_Cond, _Tp>::type;

Putting the enable_if in the constructor's argument list does not help either. Compiled with GCC 8 with -std=c++17. Here is a Godbolt link with the code.


Solution

  • You need to make std::enable_if dependent on template parameters of the constructor not the class:

    template <class T = Traits, typename = std::enable_if_t<T::is_const>>
    thing(const thing<non_const_traits_type>& other) :
        ptr_(const_cast<ptr_type>(other.ptr_)) {}