Search code examples
c++templatestype-deductiondeduction-guide

Deducing const template argument from mutable alternative


When a function takes a std::span of const T, it cannot deduce the span type from a mutable span that is being passed in.
Is it however possible for a user-defined type to allow the deductionn of the const alternative from the mutable alternative (maybe through the help of deduction guides)?

template<typename T>
struct MySpan
{};

template<typename T>
void takingConst(std::span<const T>)
{}
template<typename T>
void takingMyConst(MySpan<const T>)
{}

int square(int num) 
{
    std::span<int> stdSpan{};
    takingConst(std::span<const int>(stdSpan));

    MySpan<int> mySpan{};
    takingMyConst(mySpan); //Any way to make this work?
}

Solution

  • Make the non-const MySpan<T> inherit from MySpan<const T>.

    Here's a way that is easy to understand:

    template<typename T>
    struct MySpan : public MySpan<const T> {
        MySpan() = default;
        MySpan(T* ptr, std::size_t size) : MySpan<const T>(ptr, size) {}
        T* data() const { return const_cast<T*>(MySpan<const T>::data()); }
        T& operator[](std::size_t i) const { return data()[i]; }
        using MySpan<const T>::size;
    };
    
    template<typename T>
    struct MySpan<const T> {
        MySpan() = default;
        MySpan(const T* ptr, std::size_t size) : ptr(ptr), sz(size) {}
        const T* data() const { return ptr; }
        const T& operator[](std::size_t i) const { return data()[i]; }
        std::size_t size() const { return sz; }
    private:
        const T* ptr = nullptr;
        std::size_t sz = 0;
    };
    

    Here's a way that doesn't duplicate code:

    template<typename T>
    struct MySpan;
    
    namespace detail {
        template<typename T>
        struct my_span_members {
        private:
            T* ptr = nullptr;
            std::size_t sz = 0;
            template<typename>
            friend struct ::MySpan;
        };
    }
    
    template<typename T>
    struct MySpan : std::conditional_t<std::is_const_v<T>, detail::my_span_members<T>, MySpan<const T>> {
    private:
        using base = std::conditional_t<std::is_const_v<T>, detail::my_span_members<T>, MySpan<const T>>;
    public:
        MySpan() = default;
        MySpan(T* ptr, std::size_t size) : base{ static_cast<const T*>(ptr), size } {}
        template<std::size_t N>
        MySpan(std::type_identity_t<T>(& arr)[N]) : MySpan(arr, N) {}
        // ...
        T* data() const { return const_cast<T*>(base::ptr); }
        std::size_t size() const { return base::sz; }
    
        T& operator[](std::size_t i) const { return data()[i]; }
        // ...
    };