Skip to content

strong::range modifier invalid iterator specializations for implicit casts #56

@T-Maxxx

Description

@T-Maxxx

I'm trying to create strong std::vector<std::string> type implicitly convertible to strong std::span<const std::vector> type.

STL example

const std::vector<std::string> foo = { "abc", "cde", "efg" };
const std::span<const std::string> bar = foo; // Implicitly convertible even when passing vector to function accepting spans only.

std::vector<std::string> dest;
std::copy( bar.begin(), bar.end(), std::back_inserter( dest ) ); // OK
std::ranges::copy( bar, std::back_inserter( dest ) ); // Seems OK, didn't tried C:

Strong example

Foreward some of our custom modifiers

template<class T>
concept SpanLike = requires( T t, const T ct ) {
    typename T::size_type;
    typename T::value_type;
    typename T::reference;
    ct.subspan( typename T::size_type(), typename T::size_type() );
    { ct.empty() } -> std::same_as<bool>;
    { ct.front() } -> std::same_as<typename T::reference>;
    { ct.back() } -> std::same_as<typename T::reference>;
};

template<class T>
concept StrongSpanLike = strong::is_strong_type<T>::value && SpanLike<strong::underlying_type_t<T>>;

class StdSpan {
public:
    template<StrongSpanLike StrongSpan>
    class modifier
        : public strong::range::modifier<StrongSpan>,
          public strong::indexed<typename strong::underlying_type_t<StrongSpan>::size_type>::template modifier<StrongSpan> {
        using underlying = strong::underlying_type_t<StrongSpan>;
        using value_type = typename underlying::value_type;
        using size_type = typename underlying::size_type;
        using reference = typename underlying::reference;

    public:
        /// std::span::subspan(size_type, size_type) const
        [[nodiscard]] constexpr StrongSpan subspan( size_type Offset, size_type Count = std::dynamic_extent ) const
            noexcept( noexcept( std::declval<const underlying &>().subspan( size_type(), size_type() ) ) )
        {
            const auto &self = static_cast<const StrongSpan &>( *this );
            return StrongSpan { value_of( self ).subspan( Offset, Count ) };
        }

        /// std::span::empty() const
        [[nodiscard]] constexpr bool empty() const noexcept( noexcept( std::declval<const underlying &>().empty() ) )
        {
            const auto &self = static_cast<const StrongSpan &>( *this );
            return value_of( self ).empty();
        }

        /// std::span::front() const
        [[nodiscard]] constexpr reference front() const
            noexcept( noexcept( std::declval<const underlying &>().front() ) )
        {
            const auto &self = static_cast<const StrongSpan &>( *this );
            return value_of( self ).front();
        }

        /// std::span::back() const
        [[nodiscard]] constexpr reference back() const noexcept( noexcept( std::declval<const underlying &>().back() ) )
        {
            const auto &self = static_cast<const StrongSpan &>( *this );
            return value_of( self ).back();
        }
    };
};
template<class T>
concept VectorLike = requires( T t, const T ct ) {
    typename T::size_type;
    typename T::reference;
    typename T::const_reference;
    { ct.empty() } -> std::same_as<bool>;
    { t.front() } -> std::same_as<typename T::reference>;
    { ct.front() } -> std::same_as<typename T::const_reference>;
    { t.back() } -> std::same_as<typename T::reference>;
    { ct.back() } -> std::same_as<typename T::const_reference>;
    t.reserve( typename T::size_type() );
};

template<class T>
concept StrongVectorLike = strong::is_strong_type<T>::value && VectorLike<strong::underlying_type_t<T>>;
class StdVector {
public:
    template<StrongVectorLike T>
    class modifier : public strong::default_constructible::modifier<T>,
                     public strong::equality::modifier<T>,
                     public strong::range::modifier<T>,
                     public strong::indexed<typename strong::underlying_type_t<T>::size_type>::template modifier<T> {
        using underlying = strong::underlying_type_t<T>;
        using size_type = typename underlying::size_type;
        using reference = typename underlying::reference;
        using const_reference = typename underlying::const_reference;

    public:
        /// std::vector::empty().
        [[nodiscard]] constexpr bool empty() const noexcept( noexcept( std::declval<underlying &>().empty() ) )
        {
            const auto &self = static_cast<const T &>( *this );
            return value_of( self ).empty();
        }

        /// std::vector::front() const;
        [[nodiscard]] constexpr const_reference front() const
            noexcept( noexcept( std::declval<const underlying &>().front() ) )
        {
            const auto &self = static_cast<const T &>( *this );
            return value_of( self ).front();
        }

        /// std::vector::front();
        [[nodiscard]] constexpr reference front() noexcept( noexcept( std::declval<underlying &>().front() ) )
        {
            auto &self = static_cast<T &>( *this );
            return value_of( self ).front();
        }

        /// std::vector::back() const;
        [[nodiscard]] constexpr const_reference back() const
            noexcept( noexcept( std::declval<const underlying &>().back() ) )
        {
            const auto &self = static_cast<const T &>( *this );
            return value_of( self ).back();
        }

        /// std::vector::back();
        [[nodiscard]] constexpr reference back() noexcept( noexcept( std::declval<underlying &>().back() ) )
        {
            auto &self = static_cast<T &>( *this );
            return value_of( self ).back();
        }

        /// std::vector::reserve()
        constexpr void
        reserve( size_type new_cap ) noexcept( noexcept( std::declval<underlying &>().reserve( size_type() ) ) )
        {
            auto &self = static_cast<T &>( *this );
            value_of( self ).reserve( new_cap );
        }

        /// std::back_inserter(underlying container).
        [[nodiscard]] constexpr std::back_insert_iterator<underlying> back_inserter()
        {
            auto &self = static_cast<T &>( *this );
            return std::back_inserter( value_of( self ) );
        }

        /// std::vector::emplace_back(...)
        template<class... Args>
        void emplace_back( Args &&...args )
        {
            auto &self = static_cast<T &>( *this );
            value_of( self ).emplace_back( std::forward<Args>( args )... );
        }
    };
};

Now types:

using VectorViewType = strong::type<std::span<const std::string>, struct VectorViewTypeTag_, StdSpan>;
using VectorType = strong::type<std::vector<std::string>, struct VectorTypeTag_, strong::implicitly_convertible_to<VectorViewType>, StdVector>;

And usage example:

VectorType foo{ "abc", "cde", "efg" };
VectorViewType bar{ foo };

VectorType dest;
std::copy( bar.begin(), bar.end(), dest.back_inserter() ); 
// v14 - OK,
// v15 - compile error not really interesting which one because:
// main@13cd075b: 
//     error: implicit instantiation of undefined template 'strong::range::modifier<strong::type<std::span<const std::string>, VectorViewTypeTag_, StdSpan>>'
std::ranges::copy( bar, dest.back_inserter() ); // Didn't tried, expecting same error here C:

Complete (a bit redacted) error output:

Span.hpp:36:18: error: implicit instantiation of undefined template 'strong::range::modifier<strong::type<std::span<const std::string>, VectorViewTag_, StdSpan>>'
   36 |         : public strong::range::modifier<StrongSpan>,
      |                  ^
include/strong_type/type.hpp:66:21: note: in instantiation of template class 'StdSpan::modifier<strong::type<std::span<const std::string>, VectorViewTag_, StdSpan>>' requested here
   66 | class type : public modifier<M, type<T, Tag, M...>> ... {
      |                     ^
include/strong_type/implicitly_convertible_to.hpp:33:21: note: in instantiation of template class 'strong::type<std::span<const std::string>, VectorViewTag_, StdSpan>' requested here
   33 |     void_t<decltype(static_cast<D>(std::declval<const T&>()))>
      |                     ^
include/strong_type/implicitly_convertible_to.hpp:49:23: note: during template argument deduction for class template partial specialization 'implicit_converter<strong::type<T, Tag, Ms...>, D, void_t<decltype(static_cast<D>(std::declval<const T &>()))>>' [with T = std::vector<std::string>, Tag = VectorTypeTag_, Ms = <strong::implicitly_convertible_to<strong::type<std::span<const std::string>, VectorViewTag_, StdSpan>>, StdVector>, D = strong::type<std::span<const std::string>, VectorViewTag_, StdSpan>]
   49 |     struct modifier : impl::implicit_converter<T, Ts>...
      |                       ^
include/strong_type/implicitly_convertible_to.hpp:49:23: note: in instantiation of template class 'strong::impl::implicit_converter<strong::type<std::vector<std::string>, VectorTypeTag_, strong::implicitly_convertible_to<strong::type<std::span<const std::string>, VectorViewTag_, StdSpan>>, StdVector>, strong::type<std::span<const std::string>, VectorViewTag_, StdSpan>>' requested here
include/strong_type/type.hpp:66:21: note: in instantiation of template class 'strong::implicitly_convertible_to<strong::type<std::span<const std::string>, VectorViewTag_, StdSpan>>::modifier<strong::type<std::vector<std::string>, VectorTypeTag_, strong::implicitly_convertible_to<strong::type<std::span<const std::string>, VectorViewTag_, StdSpan>>, StdVector>>' requested here
   66 | class type : public modifier<M, type<T, Tag, M...>> ... {
      |                     ^
source.cpp:29:24: note: in instantiation of template class 'strong::type<std::vector<std::string>, VectorTypeTag_, strong::implicitly_convertible_to<strong::type<std::span<const std::string>, VectorViewTag_, StdSpan>>, StdVector>' requested here
   29 | [[nodiscard]] VectorType make_vector( Parameters &&...parameters )
      |                          ^
include/strong_type/range.hpp:139:11: note: template is declared here
  139 |     class modifier;

Compiler: Clang 19.1.7, C++20.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions