C++ Smart Pointer Type Traits 구현하기
Short Coding

C++ Smart Pointer Type Traits 구현하기

Woon2World :: Programming & Art Life

 

 

생 포인터인지 검사하는 것은 std 라이브러리에서 가능하다.
스마트 포인터인지도 검사 가능하게 만들어보자.

 

 

 

 


1
2
3
4
5
6
int main()
{
    static_assert(woon2::is_unique_ptr_v<std::unique_ptr<int>>);
    static_assert(woon2::is_shared_ptr_v<std::shared_ptr<int>>);
    static_assert(woon2::is_smart_ptr_v<std::unique_ptr<int>>);
}
cs

 

 

예제 코드

// ==========================================================================
// Test Code
// ==========================================================================
 
#include <iostream>
#include "smart_pointer_type_trait.hpp"
 
template < typename Ty >
struct Sptr : std::shared_ptr< Ty >
{
    Sptr( const Ty& val ) : std::shared_ptr< Ty >new Ty{ val } ) {}
};
 
template < typename Ty >
struct Uptr : std::unique_ptr< Ty >
{
    Uptr( const Ty& val ) : std::unique_ptr< Ty >new Ty{ val } ) {}
};
 
int main()
{
    auto check = []( auto&& target )
    {
        std::cout << typeid( target ).name() << '\n';
        std::cout << "is_shared_ptr: " << woon2::is_shared_ptr_v< decltype( target ) > << '\n';
        std::cout << "is_unique_ptr: " << woon2::is_unique_ptr_v< decltype( target ) > << '\n';
        std::cout << "is_smart_ptr: " << woon2::is_smart_ptr_v< decltype( target ) > << '\n';
        std::cout << "is_shared_ptr_soft: " << woon2::is_shared_ptr_soft_v< decltype( target ) > << '\n';
        std::cout << "is_unique_ptr_soft: " << woon2::is_unique_ptr_soft_v< decltype( target ) > << '\n';
        std::cout << "is_smart_ptr_soft: " << woon2::is_smart_ptr_soft_v< decltype( target ) > << '\n';
        std::cout << "is_pointable: " << woon2::is_pointable_v< decltype( target ) > << '\n';
        std::cout << "\n\n\n";
    };
 
    const auto a = std::make_unique< int >3 );
    check( a );
 
    Uptr< int > b{ 2 };
    check( b );
 
    auto c = std::make_shared< int >4 );
    check( c );
 
    Sptr< int > d{ 5 };
    check( d );
 
    int e = 8;
    check( e );
 
    int* f = &e;
    check( f );
}
cs

 

 

 

 

// ==========================================================================
// Implementation: easily changable dynamic allocation policy.
// Just retype the pointer type in line 93.
// ==========================================================================
 
#include <iostream>
#include "smart_pointer_type_trait.hpp"
 
template < typename Ptr >
decltype( auto ) get_raw_pointer( Ptr&& ptr )
{
    if constexpr ( std::is_pointer_v< std::decay_t< Ptr > > ) return std::forward< Ptr >( ptr );
    else return ptr.operator->();
}
 
// ======================================================================
// a wrapper class of a pointer, for safe compilation.
// if the wrapped class doesn't have the required method,
// try to behave like the expected behavior from the name.
// if it fails, it eventually does nothing. ( or static assertion fails. )
// you can get the original pointer type by using unwrapped ( pointer< Ty >::unwrapped ).
// ======================================================================
template < typename Ty, typename Ptr >
class pointer_impl
{
public:
    using unwrapped = Ptr;
 
    void reset( Ty* target = nullptr )
    {
        static_assert( woon2::has_reset_v< Ptr > || std::is_pointer_v< Ptr >,
            "pointer_impl< Ty, Ptr >::reset(): Ptr doesn't have reset()." );
 
        if constexpr ( woon2::has_reset_v< Ptr > ) impl.reset( target );
        else
        {
            delete impl;
            impl = target;
        }
    }
 
    auto release()
    {
        static_assert( woon2::has_release_v< Ptr > || std::is_pointer_v< Ptr > || woon2::is_shared_ptr_soft_v< Ptr >,
            "pointer_impl< Ty, Ptr >::release(): Ptr doesn't have release()." );
 
        if constexpr ( woon2::has_release_v< Ptr > ) return impl.release();
        else
        {
            auto ret = impl;
            reset();
            return ret;
        }
    }
 
    decltype( auto ) get_deleter() noexcept
    {
        if constexpr ( woon2::has_get_deleter_v< Ptr > ) return impl.get_deleter();
        else return nullptr;
    }
 
    decltype( auto ) get_deleter() const noexcept
    {
        if constexpr ( woon2::has_get_deleter_v< Ptr > ) return impl.get_deleter();
        else return nullptr;
    }
 
    Ty* get() noexcept
    {
        return operator->();
    }
 
    const Ty* get() const noexcept
    {
        return operator->();
    }
 
    Ty& operator*() noexcept { return *impl; }
    const Ty& operator*() const noexcept { return *impl; }
    Ty* operator->() noexcept { return get_raw_pointer( impl ); }
    const Ty* operator->() const noexcept { return get_raw_pointer( impl ); }
    operator bool() const noexcept { return static_cast<bool>( get_raw_pointer( impl ) ); }
 
    // special member functions
    pointer_impl( Ty* impl = nullptr ) : impl{ impl } {}
 
private:
    Ptr impl;
};
// pointer_impl end =====================================================
 
// ======================================================================
// **********************************************************************
// Decoupling by using keyword.
// Memory allocation policy has the only dependancy on this code.
// ======================================================================
template < typename Ty >
using pointer = pointer_impl< Ty, std::unique_ptr< Ty > >;        // can change to diffrent pointer
                                                                // if you add more pointer class,
                                                                // you can also change to the pointer class.
// **********************************************************************
// ======================================================================
 
template < typename Ty, typename ... Args >
auto make( Args&& ... args )
{
    return pointer< Ty >new Ty{ std::forward< Args >( args )... } };
}
 
struct INT
{
    INT( int impl = 0 ) : impl{ impl } { std::cout << "INT constructor called\n"; }
    ~INT() { std::cout << "INT destructor called\n"; }
    INT( const INT& other ) : impl{ other.impl } { std::cout << "INT copy constructor called\n"; }
    INT& operator=const INT& other )
    {
        if ( this != &other )
        {
            std::cout << "INT copy allocator called\n";
            impl = other.impl;
        }
 
        return *this;
    }
    INT( INT&& other ) noexcept : impl{ other.impl } { std::cout << "INT move constructor called\n"; }
    INT& operator=( INT&& other ) noexcept
    {
        if ( this != &other )
        {
            std::cout << "INT move allocator called\n";
            impl = other.impl;
        }
 
        return *this;
    }
    operator int() { return impl; }
 
    int impl;
};
 
int main()
{
    std::cout << "must call constructor ==================\n";
    auto a = make< INT >4 );
    std::cout << "========================================\n\n\n";
 
    std::cout << "get_deleter() call =====================\n";
    std::cout << typeid( a.get_deleter() ).name() << '\n';
    std::cout << "========================================\n\n\n";
 
    std::cout << "print value ============================\n";
    std::cout << "value: " << *<< '\n';
    std::cout << "========================================\n\n\n";
 
    std::cout << "must call constructor and destructor ===\n";
    a.reset( new INT{ 5 } );
    std::cout << "========================================\n\n\n";
 
    std::cout << "print value ============================\n";
    std::cout << "value: " << *<< '\n';
    std::cout << "========================================\n\n\n";
 
    std::cout << "must call destructor ===================\n";
    if constexpr ( std::is_pointer_v< decltype( a )::unwrapped > )
    {
        std::cout << "cause unwrapped pointer type was a raw pointer, called release().\n";
        a.release();
    }
}
cs

 

 

 

 

// ==========================================================================
// Implementation: added new pointer class
// this is an example of smart_pointer_type_trait.hpp's additional type trait guide.
// ==========================================================================
 
#include <iostream>
#include "smart_pointer_type_trait.hpp"
 
template< typename Ty >
class value_pointer
{
public:
    // methods
    void reset( const Ty& val = Ty{} )
    {
        if ( !raw_ptr ) raw_ptr = new Ty{ val };
        else *raw_ptr = val;
    }
 
    void reset( Ty&& val )
    {
        if ( !raw_ptr ) raw_ptr = new Ty{ std::move( val ) };
        else *raw_ptr = std::move_if_noexcept( val );
    }
 
    void release()
    {
        delete raw_ptr;
        raw_ptr = nullptr;
    }
 
    void swap( value_pointer& right )
    {
        if ( !raw_ptr ) throw std::exception{ "value_pointer< Ty >::swap(): raw_ptr was null pointer." };
        if ( !right.raw_ptr ) throw std::exception{ "value_pointer< Ty >::swap(): invalid argument( right was null pointer. )" };
 
        auto temp = std::move_if_noexcept( *right.raw_ptr );
        *right.raw_ptr = std::move_if_noexcept( *raw_ptr );
        *raw_ptr = std::move_if_noexcept( temp );
    }
 
    // operators
    Ty& operator*() { return *raw_ptr; }
    const Ty& operator*() const { return *raw_ptr; }
    Ty* operator->() { return raw_ptr; }
    const Ty* operator->() const { return raw_ptr; }
 
    // special member functions
    value_pointer( const Ty& val ) : raw_ptr{ new Ty{ val } } {}
    value_pointer( Ty&& val ) : raw_ptr{ new Ty{ std::move( val ) } } {}
    value_pointer( const value_pointer& other ) : raw_ptr{ new Ty{ *other } } {}
    value_pointer& operator=const value_pointer& other )
    {
        if ( this != &other ) reset( *other );
        return *this;
    }
    value_pointer( value_pointer&& other ) noexcept : raw_ptr{ new Ty{ std::move( *other ) } } {}
    value_pointer& operator=( value_pointer&& other ) noexcept
    {
        if ( this != &other ) reset( std::move( *other ) );
        return *this;
    }
    ~value_pointer() { if ( raw_ptr ) delete raw_ptr; }
 
private:
    Ty* raw_ptr;
};
 
template < typename Ty >
struct Vptr : value_pointer< Ty > {};
 
// =====================================================================
// *********************************************************************
// new type trait ( value_ptr )
// just retyped the pointer's name.
// *********************************************************************
// =====================================================================
 
namespace woon2
{
    namespace detail
    {
        template < typename T >
        struct is_value_pointer_impl : std::false_type {};
 
        template < typename T >
        struct is_value_pointer_impl< value_pointer< T > > : std::true_type {};
 
        template < typename T >
        std::true_type is_value_pointer_soft_impl( value_pointer< T >* );
        std::false_type is_value_pointer_soft_impl( ... );
    }
 
    template < typename T >
    using is_value_pointer = detail::is_value_pointer_impl< detail::remove_cvr_t< T > >;
 
    template < typename T >
    constexpr bool is_value_pointer_v = is_value_pointer< T >::value;
 
    template < typename T >
    using is_value_pointer_soft = decltype( detail::is_value_pointer_soft_impl( std::declval< detail::remove_cvr_t< T >* >() ) );
 
    template < typename T >
    constexpr bool is_value_pointer_soft_v = is_value_pointer_soft< T >::value;
}
 
// =====================================================================
 
struct INT
{
    INT( int impl = 0 ) : impl{ impl } { std::cout << "INT constructor called\n"; }
    ~INT() { std::cout << "INT destructor called\n"; }
    INT( const INT& other ) : impl{ other.impl } { std::cout << "INT copy constructor called\n"; }
    INT& operator=const INT& other )
    {
        if ( this != &other )
        {
            std::cout << "INT copy allocator called\n";
            impl = other.impl;
        }
 
        return *this;
    }
    INT( INT&& other ) noexcept : impl{ other.impl } { std::cout << "INT move constructor called\n"; }
    INT& operator=( INT&& other ) noexcept
    {
        if ( this != &other )
        {
            std::cout << "INT move allocator called\n";
            impl = other.impl;
        }
 
        return *this;
    }
    operator int() { return impl; }
 
    int impl;
};
 
int main()
{
    // test type traits ==============================================
    static_assert( woon2::is_pointable_v< value_pointer< int > >"is_pointable trait doesn't work." );
    static_assert( woon2::is_value_pointer_v< value_pointer< int > >"is_value_pointer trait doesn't work." );
    static_assert( woon2::is_value_pointer_soft_v< Vptr< int > >"is_value_pointer_soft trait doesn't work." );
    static_assert( !woon2::has_get_deleter_v< value_pointer< int > >"has_get_deleter trait doesn't work." );
    static_assert( woon2::has_release_v< value_pointer< int > >"has_release trait doesn't work." );
    static_assert( woon2::has_swap_v< value_pointer< int > >"has_swap trait doesn't work." );
    static_assert( woon2::has_reset_v< value_pointer< int > >"has_reset trait doesn't work." );
    // ===============================================================
 
    std::cout << "====================================================\n";
    std::cout << "value_pointer test\n";
    std::cout << "====================================================\n\n\n";
 
    try {
        std::cout << "construct a ========================================\n";
        value_pointer< INT > a{ 3 };
        std::cout << "====================================================\n\n\n";
 
        std::cout << "construct b ========================================\n";
        value_pointer< INT > b{ 4 };
        std::cout << "====================================================\n\n\n";
 
        std::cout << "print values =======================================\n";
        std::cout << "a: " << *<< ", b: " << *<< '\n';
        std::cout << "====================================================\n\n\n";
 
        std::cout << "swap a, b ==========================================\n";
        a.swap( b );
        std::cout << "====================================================\n\n\n";
 
        std::cout << "print values =======================================\n";
        std::cout << "a: " << *<< ", b: " << *<< '\n';
        std::cout << "====================================================\n\n\n";
 
        std::cout << "release a ==========================================\n";
        a.release();
        std::cout << "====================================================\n\n\n";
 
        std::cout << "reset a with b =====================================\n";
        a.reset( *b );
        std::cout << "====================================================\n\n\n";
 
        std::cout << "print values =======================================\n";
        std::cout << "a: " << *<< ", b: " << *<< '\n';
        std::cout << "====================================================\n\n\n";
 
        std::cout << "must call destructors ==============================\n";
    }
    catch ( const std::exception& e )
    {
        std::cout << e.what();
        exit( -1 );
    }
}
cs

 

 

활용 방안

개발 중 unique_ptr를 사용할지, shared_ptr를 사용할지, 다른 RAII 객체를 사용할지, 아니면 아예 생 포인터를 사용할지 선택이 많이 오간다.

따라서 나중에 선택을 바꾸더라도 상관 없도록 타입 체크를 통해 컴파일 될 모든 코드들을 생성시켜 놓는다.

동적 할당 함수나, 포인터를 통해 상호작용하는 어떠한 함수(기왕이면 메모리 내용 자체를 건드리는) 등에 static_assertion, if constexpr 등과 같이 적용할 수 있을 것이다.

코드

https://github.com/MyeongWoonJang/SmartPointerTypeTrait/tree/main

 

GitHub - MyeongWoonJang/SmartPointerTypeTrait: C++ Type Traits for Smart Pointer

C++ Type Traits for Smart Pointer. Contribute to MyeongWoonJang/SmartPointerTypeTrait development by creating an account on GitHub.

github.com

// ==========================================================================
// Type Traits for smart pointers
// Supporting is_unique_ptr< T >, is_shared_ptr< T >, is_smart_ptr< T >, is_ptr< T >
// Usage is like std::is_pointer< T >.
// Soft version type traits additionally evalute a derived class from a pointer as true.
// Just write "soft" after a type_trait to detect inheritance too.
// ==========================================================================
 
#ifndef _smart_pointer_type_trait
#define _smart_pointer_type_trait
 
#include <memory>
 
namespace woon2
{
    namespace detail
    {
        // for removing const, volatile, reference.
        // via this, type traits have no dependency on qualifiers.
        template < typename T >
        using remove_cvr_t = std::remove_cv_t< std::remove_reference_t< T > >;
    }
 
    // =======================================================================================
    // shared_ptr type trait
    // =======================================================================================
    namespace detail
    {
        template < typename T >
        struct is_shared_ptr_impl : std::false_type {};
 
        template < typename T >
        struct is_shared_ptr_impl< std::shared_ptr< T > > : std::true_type {};
 
        template < typename T >
        std::true_type is_shared_ptr_soft_impl( const std::shared_ptr< T >* );
        std::false_type is_shared_ptr_soft_impl( ... );
    }
 
    template < typename T >
    using is_shared_ptr = detail::is_shared_ptr_impl< detail::remove_cvr_t< T > >;
 
    template < typename T >
    constexpr bool is_shared_ptr_v = is_shared_ptr< T >::value;
 
    template < typename T >
    using is_shared_ptr_soft = decltype( detail::is_shared_ptr_soft_impl( std::declval< detail::remove_cvr_t< T >* >() ) );
 
    template < typename T >
    constexpr bool is_shared_ptr_soft_v = is_shared_ptr_soft< T >::value;
 
    // shared_ptr type trait end =============================================================
 
    // =======================================================================================
    // unique_ptr type trait
    // =======================================================================================
    namespace detail
    {
        template < typename T >
        struct is_unique_ptr_impl : std::false_type {};
 
        template < typename T, typename Dx >
        struct is_unique_ptr_impl< std::unique_ptr< T, Dx > > : std::true_type {};
 
        template < typename T, typename Dx >
        std::true_type is_unique_ptr_soft_impl( std::unique_ptr< T, Dx >* );
        std::false_type is_unique_ptr_soft_impl( ... );
    }
 
    template < typename T >
    using is_unique_ptr = detail::is_unique_ptr_impl< detail::remove_cvr_t< T > >;
 
    template < typename T >
    constexpr bool is_unique_ptr_v = is_unique_ptr< T >::value;
 
    template < typename T >
    using is_unique_ptr_soft = decltype( detail::is_unique_ptr_soft_impl( std::declval< detail::remove_cvr_t< T >* >() ) );
 
    template < typename T >
    constexpr bool is_unique_ptr_soft_v = is_unique_ptr_soft< T >::value;
 
    // unique_ptr type trait end =============================================================
 
 
    // =======================================================================================
    // united type trait ( smart pointers )
    // =======================================================================================
    template < typename T >
    using is_smart_ptr = std::conditional_t<
        is_shared_ptr_v< T >,
        std::true_type,
        std::conditional_t<
            is_unique_ptr_v< T >,
            std::true_type,
            std::false_type
            >
        >;
 
    template < typename T >
    constexpr bool is_smart_ptr_v = is_smart_ptr< T >::value;
    
    namespace detail
    {
        template < typename T, typename Dx >
        std::true_type is_smart_ptr_soft_impl( std::unique_ptr< T, Dx >* );
        template < typename T >
        std::true_type is_smart_ptr_soft_impl( std::shared_ptr< T >* );
        std::false_type is_smart_ptr_soft_impl( ... );
    }
 
    template < typename T >
    using is_smart_ptr_soft = decltype( detail::is_smart_ptr_soft_impl( std::declval< detail::remove_cvr_t< T >* >() ) );
 
    template < typename T >
    constexpr bool is_smart_ptr_soft_v = is_smart_ptr_soft< T >::value;
 
    // united type trait ( smart pointers ) end ==============================================
 
    // =======================================================================================
    // united type trait ( all pointable classes )
    // =======================================================================================
    namespace detail
    {
        template < typename T, typename _ = void >
        struct is_pointable_impl : std::false_type {};
 
        template < typename T >
        struct is_pointable_impl< T* > : std::true_type {};
 
        template < typename ... Ts >
        struct is_pointable_helper {};
 
        template < typename T >
        struct is_pointable_impl< T,
            std::conditional_t<
                false,
                is_pointable_helper<
                    decltype( std::declval< T >().operator->() ),
                    decltype( std::declval< T >().operator*() )
                    >,
                void
                >
            > : std::true_type {};
    }
 
    template < typename T >
    using is_pointable = detail::is_pointable_impl< detail::remove_cvr_t< T > >;
 
    template < typename T >
    constexpr bool is_pointable_v = is_pointable< T >::value;
 
    // united type trait ( all pointable classes ) end =======================================
 
    // method trait ( trace if a pointer class has a specified method. ) =====================
    namespace detail
    {
        // has_reset detail ==================================================================
        template < typename T, typename _ = void >
        struct has_reset_impl : std::false_type {};
 
        template < typename ... Ts >
        struct has_reset_helper {};
 
        template < typename T >
        struct has_reset_impl< T, std::conditional_t<
            false,
            has_reset_helper<
                decltype( std::declval< T >().reset() )
                >,
            void
            >
        > : std::true_type {};
        // has_reset detail end ==============================================================
 
        // has_release detail ================================================================
        template < typename T, typename _ = void >
        struct has_release_impl : std::false_type {};
 
        template < typename ... Ts >
        struct has_release_helper {};
 
        template < typename T >
        struct has_release_impl< T, std::conditional_t<
            false,
            has_release_helper<
                decltype( std::declval< T >().release() )
                >,
            void
            >
        > : std::true_type {};
        // has_release detail end ============================================================
 
        // has_get_deleter detail ============================================================
        template < typename T, typename _ = void >
        struct has_get_deleter_impl : std::false_type {};
        
        template < typename ... Ts >
        struct has_get_deleter_helper {};
 
        template < typename T >
        struct has_get_deleter_impl< T, std::conditional_t<
            false,
            has_get_deleter_helper<
                decltype( std::declval< T >().get_deleter() ),
                decltype( std::declval< T >().get_deleter() )
                >,
            void
            >
        > : std::true_type {};
        // has_get_deleter detail end=========================================================
 
        // has_swap detail ===================================================================
        template < typename T, typename _ = void >
        struct has_swap_impl : std::false_type {};
 
        template < typename ... Ts >
        struct has_swap_helper {};
 
        template < typename T >
        struct has_swap_impl< T, std::conditional_t<
            false,
            has_swap_helper<
                decltype( std::declval< T >().swap( std::declval< std::add_lvalue_reference_t< T > >() ) )
                >,
            void
            >
        > : std::true_type {};
        // has_swap detail end ===============================================================
    } // detail
 
    template < typename T >
    using has_reset = detail::has_reset_impl< detail::remove_cvr_t< T > >;
 
    template < typename T >
    constexpr bool has_reset_v = has_reset< T >::value;
 
    template < typename T >
    using has_release = detail::has_release_impl< detail::remove_cvr_t< T > >;
 
    template < typename T >
    constexpr bool has_release_v = has_release< T >::value;
 
    template < typename T >
    using has_get_deleter = detail::has_get_deleter_impl< detail::remove_cvr_t< T > >;
 
    template < typename T >
    constexpr bool has_get_deleter_v = has_get_deleter< T >::value;
 
    template < typename T >
    using has_swap = detail::has_swap_impl< detail::remove_cvr_t< T > >;
 
    template < typename T >
    constexpr bool has_swap_v = has_swap< T >::value;
 
    // method trait end ======================================================================
 
    // =======================================================================================
    // Additional pointer type trait
    // You can add your own pointer class's type trait.
    // This is the template for that.
    // Can be detected by is_pointable< T >
    // =======================================================================================
 
    // just for sample, it will be better for you to make your pointer class be in another header file.
    template < typename T >
    struct your_pointer {};
    // /sample
    // just alter all "your_pointer"s to your pointer class's name.
 
    namespace detail
    {
        template < typename T >
        struct is_your_pointer_impl : std::false_type {};
 
        template < typename T >
        struct is_your_pointer_impl< your_pointer< T > > : std::true_type {};
 
        template < typename T >
        std::true_type is_your_pointer_soft_impl( your_pointer< T >* );
        std::false_type is_your_pointer_soft_impl( ... );
    }
 
    template < typename T >
    using is_your_pointer = detail::is_your_pointer_impl< detail::remove_cvr_t< T > >;
 
    template < typename T >
    constexpr bool is_your_pointer_v = is_your_pointer< T >::value;
 
    template < typename T >
    using is_your_pointer_soft = decltype( detail::is_your_pointer_soft_impl( std::declval< detail::remove_cvr_t< T >* >() ) );
 
    template < typename T >
    constexpr bool is_your_pointer_soft_v = is_your_pointer_soft< T >::value;
 
    // additional pointer type trait end =============================================================
}
 
#endif // _smart_pointer_type_trait
cs

참고 자료

https://stackoverflow.com/questions/65752626/concept-for-smart-pointers

 

Concept for smart pointers

Given a class template like this: template<typename T> struct foo { T data; }; How may one determine whether T is a smart pointer such as std::shared_ptr<T_underlying> using C++20

stackoverflow.com

https://modoocode.com/294

 

씹어먹는 C++ - <16 - 3. 타입을 알려주는 키워드 decltype 와 친구 std::declval>

 

modoocode.com

https://en.cppreference.com/w/cpp/types/conditional

 

std::conditional - cppreference.com

template< bool B, class T, class F > struct conditional; (since C++11) Provides member typedef type, which is defined as T if B is true at compile time, or as F if B is false. The behavior of a program that adds specializations for conditional is undefined

en.cppreference.com

https://stackoverflow.com/questions/12042824/how-to-write-a-type-trait-is-container-or-is-vector

 

How to write a type trait `is_container` or `is_vector`?

Is it possible to write a type trait whose value is true for all common STL structures (e.g., vector, set, map, ...)? To get started, I'd like to write a type trait that is true for a vector and f...

stackoverflow.com

https://en.cppreference.com/w/cpp/language/sfinae

 

SFINAE - cppreference.com

"Substitution Failure Is Not An Error" This rule applies during overload resolution of function templates: When substituting the explicitly specified or deduced type for the template parameter fails, the specialization is discarded from the overload set in

en.cppreference.com