수색…
소개
형식 지우기는 클라이언트에서 기본 형식 정보를 숨기면서 다양한 기본 형식에 대한 일관된 인터페이스를 제공 할 수있는 형식을 만드는 기술 집합입니다. std::function<R(A...)>
, 다양한 유형의 호출 가능 객체를 보유 할 수있는 능력은 C ++에서 가장 잘 알려진 유형 삭제 유형의 예입니다.
기본 메커니즘
유형 삭제는 공통 기본 클래스에서 파생되지는 않았지만 객체를 사용하는 코드에서 객체 유형을 숨기는 방법입니다. 그렇게함으로써 정적 다형성 (템플릿 : 사용 장소에서 정확한 유형은 컴파일시 알려야하지만 정의시 인터페이스를 준수하도록 선언 할 필요는 없음) 및 동적 다형성 (상속 및 가상 함수, 사용 장소에서 정확한 유형은 컴파일시 알 필요는 없지만 정의시 인터페이스를 준수하도록 선언해야 함).
다음 코드는 유형 삭제의 기본 메커니즘을 보여줍니다.
#include <ostream>
class Printable
{
public:
template <typename T>
Printable(T value) : pValue(new Value<T>(value)) {}
~Printable() { delete pValue; }
void print(std::ostream &os) const { pValue->print(os); }
private:
Printable(Printable const &) /* in C++1x: =delete */; // not implemented
void operator = (Printable const &) /* in C++1x: =delete */; // not implemented
struct ValueBase
{
virtual ~ValueBase() = default;
virtual void print(std::ostream &) const = 0;
};
template <typename T>
struct Value : ValueBase
{
Value(T const &t) : v(t) {}
virtual void print(std::ostream &os) const { os << v; }
T v;
};
ValueBase *pValue;
};
사용 사이트에서는 가상 함수가있는 기본 클래스와 마찬가지로 위의 정의 만 표시 할 수 있어야합니다. 예 :
#include <iostream>
void print_value(Printable const &p)
{
p.print(std::cout);
}
이 템플릿이 아니라 오직 필요로하는 일반 함수는 헤더 파일에 선언하고, (그 정의 템플릿, 사용의 장소에서 볼 수 있어야 달리) 구현 파일에서 정의 할 수 있습니다.
구체적인 유형의 정의에서, Printable
에 관해서는 알 필요가 없으며, 템플릿과 마찬가지로 인터페이스에 순응해야합니다.
struct MyType { int i; };
ostream& operator << (ostream &os, MyType const &mc)
{
return os << "MyType {" << mc.i << "}";
}
이제이 클래스의 객체를 위에 정의 된 함수에 전달할 수 있습니다.
MyType foo = { 42 };
print_value(foo);
수동 vtable로 일반 유형으로 지우기
C ++은 Regular 타입 (또는 적어도 Pseudo-Regular)으로 알려져 있습니다.
정규 유형은 복사 또는 이동을 통해 생성 및 할당되고 배정되고 파괴 될 수 있고 동일하게 비교 될 수있는 유형입니다. 인수없이 구성 할 수도 있습니다. 마지막으로, 다양한 std
알고리즘과 컨테이너에서 매우 유용한 몇 가지 다른 연산을 지원합니다.
이것이 루트 페이퍼 이지만 C ++ 11에서는 std::hash
지원을 추가하려고합니다.
여기에 수동으로 vtable 접근 방식을 사용하여 삭제합니다.
using dtor_unique_ptr = std::unique_ptr<void, void(*)(void*)>;
template<class T, class...Args>
dtor_unique_ptr make_dtor_unique_ptr( Args&&... args ) {
return {new T(std::forward<Args>(args)...), [](void* self){ delete static_cast<T*>(self); }};
}
struct regular_vtable {
void(*copy_assign)(void* dest, void const* src); // T&=(T const&)
void(*move_assign)(void* dest, void* src); // T&=(T&&)
bool(*equals)(void const* lhs, void const* rhs); // T const&==T const&
bool(*order)(void const* lhs, void const* rhs); // std::less<T>{}(T const&, T const&)
std::size_t(*hash)(void const* self); // std::hash<T>{}(T const&)
std::type_info const&(*type)(); // typeid(T)
dtor_unique_ptr(*clone)(void const* self); // T(T const&)
};
template<class T>
regular_vtable make_regular_vtable() noexcept {
return {
[](void* dest, void const* src){ *static_cast<T*>(dest) = *static_cast<T const*>(src); },
[](void* dest, void* src){ *static_cast<T*>(dest) = std::move(*static_cast<T*>(src)); },
[](void const* lhs, void const* rhs){ return *static_cast<T const*>(lhs) == *static_cast<T const*>(rhs); },
[](void const* lhs, void const* rhs) { return std::less<T>{}(*static_cast<T const*>(lhs),*static_cast<T const*>(rhs)); },
[](void const* self){ return std::hash<T>{}(*static_cast<T const*>(self)); },
[]()->decltype(auto){ return typeid(T); },
[](void const* self){ return make_dtor_unique_ptr<T>(*static_cast<T const*>(self)); }
};
}
template<class T>
regular_vtable const* get_regular_vtable() noexcept {
static const regular_vtable vtable=make_regular_vtable<T>();
return &vtable;
}
struct regular_type {
using self=regular_type;
regular_vtable const* vtable = 0;
dtor_unique_ptr ptr{nullptr, [](void*){}};
bool empty() const { return !vtable; }
template<class T, class...Args>
void emplace( Args&&... args ) {
ptr = make_dtor_unique_ptr<T>(std::forward<Args>(args)...);
if (ptr)
vtable = get_regular_vtable<T>();
else
vtable = nullptr;
}
friend bool operator==(regular_type const& lhs, regular_type const& rhs) {
if (lhs.vtable != rhs.vtable) return false;
return lhs.vtable->equals( lhs.ptr.get(), rhs.ptr.get() );
}
bool before(regular_type const& rhs) const {
auto const& lhs = *this;
if (!lhs.vtable || !rhs.vtable)
return std::less<regular_vtable const*>{}(lhs.vtable,rhs.vtable);
if (lhs.vtable != rhs.vtable)
return lhs.vtable->type().before(rhs.vtable->type());
return lhs.vtable->order( lhs.ptr.get(), rhs.ptr.get() );
}
// technically friend bool operator< that calls before is also required
std::type_info const* type() const {
if (!vtable) return nullptr;
return &vtable->type();
}
regular_type(regular_type&& o):
vtable(o.vtable),
ptr(std::move(o.ptr))
{
o.vtable = nullptr;
}
friend void swap(regular_type& lhs, regular_type& rhs){
std::swap(lhs.ptr, rhs.ptr);
std::swap(lhs.vtable, rhs.vtable);
}
regular_type& operator=(regular_type&& o) {
if (o.vtable == vtable) {
vtable->move_assign(ptr.get(), o.ptr.get());
return *this;
}
auto tmp = std::move(o);
swap(*this, tmp);
return *this;
}
regular_type(regular_type const& o):
vtable(o.vtable),
ptr(o.vtable?o.vtable->clone(o.ptr.get()):dtor_unique_ptr{nullptr, [](void*){}})
{
if (!ptr && vtable) vtable = nullptr;
}
regular_type& operator=(regular_type const& o) {
if (o.vtable == vtable) {
vtable->copy_assign(ptr.get(), o.ptr.get());
return *this;
}
auto tmp = o;
swap(*this, tmp);
return *this;
}
std::size_t hash() const {
if (!vtable) return 0;
return vtable->hash(ptr.get());
}
template<class T,
std::enable_if_t< !std::is_same<std::decay_t<T>, regular_type>{}, int>* =nullptr
>
regular_type(T&& t) {
emplace<std::decay_t<T>>(std::forward<T>(t));
}
};
namespace std {
template<>
struct hash<regular_type> {
std::size_t operator()( regular_type const& r )const {
return r.hash();
}
};
template<>
struct less<regular_type> {
bool operator()( regular_type const& lhs, regular_type const& rhs ) const {
return lhs.before(rhs);
}
};
}
라이브 예제 .
이러한 일반 유형은위한 키로서 사용 할 수 있습니다 std::map
또는 std::unordered_map
같은 키에 대한 정기적 것을 허용합니다
std::map<regular_type, std::any>
기본적으로 anothing에서 copyable에 이르는지도 일 것입니다.
any
와는 달리, regular_type
은 작은 객체 최적화를 수행하지 않으며 원본 데이터를 다시 가져 오는 것을 지원하지 않습니다. 원래 유형을 다시 얻는 것은 어렵지 않습니다.
소규모 개체 최적화를 위해서는 regular_type
내에 정렬 된 저장소 버퍼를 저장하고 ptr
삭제자를 조심스럽게 조정하여 개체 만 삭제하고 삭제하지 않도록해야합니다.
make_dtor_unique_ptr
에서 시작하여 버퍼에 데이터를 저장하는 방법과 버퍼가없는 경우 힙에 데이터를 저장하는 방법을 가르쳐줍니다. 충분할 수도 있습니다.
이동 전용`std :: function`
std::function
type은 몇 가지 연산을 지운다. 필요한 것 중 하나는 저장된 값을 복사 할 수 있다는 것입니다.
이것은 고유 한 ptrs를 저장하는 lambdas와 같은 몇 가지 상황에서 문제를 일으 킵니다. 복사 작업이 중요하지 않은 컨텍스트에서 스레드에 작업을 디스패치하는 스레드 풀과 같이 std::function
를 사용하는 경우이 요구 사항으로 인해 오버 헤드가 추가 될 수 있습니다.
특히, std::packaged_task<Sig>
는 이동 전용 인 호출 가능 객체입니다. std::packaged_task<void(Args...)>
std::packaged_task<R(Args...)>
를 저장할 수 있지만 꽤 무겁고 가려운 방법으로 이동 전용을 만들 수 있습니다 호출 가능 유형 삭제 클래스.
따라서 task
. 이것은 간단한 std::function
유형을 작성하는 방법을 보여줍니다. 복사 생성자를 생략했습니다 ( clone
메소드를 details::task_pimpl<...>
추가하는 작업이 포함됩니다).
template<class Sig>
struct task;
// putting it in a namespace allows us to specialize it nicely for void return value:
namespace details {
template<class R, class...Args>
struct task_pimpl {
virtual R invoke(Args&&...args) const = 0;
virtual ~task_pimpl() {};
virtual const std::type_info& target_type() const = 0;
};
// store an F. invoke(Args&&...) calls the f
template<class F, class R, class...Args>
struct task_pimpl_impl:task_pimpl<R,Args...> {
F f;
template<class Fin>
task_pimpl_impl( Fin&& fin ):f(std::forward<Fin>(fin)) {}
virtual R invoke(Args&&...args) const final override {
return f(std::forward<Args>(args)...);
}
virtual const std::type_info& target_type() const final override {
return typeid(F);
}
};
// the void version discards the return value of f:
template<class F, class...Args>
struct task_pimpl_impl<F,void,Args...>:task_pimpl<void,Args...> {
F f;
template<class Fin>
task_pimpl_impl( Fin&& fin ):f(std::forward<Fin>(fin)) {}
virtual void invoke(Args&&...args) const final override {
f(std::forward<Args>(args)...);
}
virtual const std::type_info& target_type() const final override {
return typeid(F);
}
};
};
template<class R, class...Args>
struct task<R(Args...)> {
// semi-regular:
task()=default;
task(task&&)=default;
// no copy
private:
// aliases to make some SFINAE code below less ugly:
template<class F>
using call_r = std::result_of_t<F const&(Args...)>;
template<class F>
using is_task = std::is_same<std::decay_t<F>, task>;
public:
// can be constructed from a callable F
template<class F,
// that can be invoked with Args... and converted-to-R:
class= decltype( (R)(std::declval<call_r<F>>()) ),
// and is not this same type:
std::enable_if_t<!is_task<F>{}, int>* = nullptr
>
task(F&& f):
m_pImpl( make_pimpl(std::forward<F>(f)) )
{}
// the meat: the call operator
R operator()(Args... args)const {
return m_pImpl->invoke( std::forward<Args>(args)... );
}
explicit operator bool() const {
return (bool)m_pImpl;
}
void swap( task& o ) {
std::swap( m_pImpl, o.m_pImpl );
}
template<class F>
void assign( F&& f ) {
m_pImpl = make_pimpl(std::forward<F>(f));
}
// Part of the std::function interface:
const std::type_info& target_type() const {
if (!*this) return typeid(void);
return m_pImpl->target_type();
}
template< class T >
T* target() {
return target_impl<T>();
}
template< class T >
const T* target() const {
return target_impl<T>();
}
// compare with nullptr :
friend bool operator==( std::nullptr_t, task const& self ) { return !self; }
friend bool operator==( task const& self, std::nullptr_t ) { return !self; }
friend bool operator!=( std::nullptr_t, task const& self ) { return !!self; }
friend bool operator!=( task const& self, std::nullptr_t ) { return !!self; }
private:
template<class T>
using pimpl_t = details::task_pimpl_impl<T, R, Args...>;
template<class F>
static auto make_pimpl( F&& f ) {
using dF=std::decay_t<F>;
using pImpl_t = pimpl_t<dF>;
return std::make_unique<pImpl_t>(std::forward<F>(f));
}
std::unique_ptr<details::task_pimpl<R,Args...>> m_pImpl;
template< class T >
T* target_impl() const {
return dynamic_cast<pimpl_t<T>*>(m_pImpl.get());
}
};
이 라이브러리를 유용하게 만들려면 작은 버퍼 최적화를 추가하여 힙에 모든 호출 가능 함수를 저장하지 않아야합니다.
SBO를 추가하려면 기본 이외의 task(task&&)
, 클래스 내의 일부 std::aligned_storage_t
, destroy 전용으로 설정할 수있는 deleter가있는 m_pImpl
unique_ptr
(메모리를 힙으로 반환하지 않음) 및 emplace_move_to( void* ) = 0
task_pimpl
에서 emplace_move_to( void* ) = 0
으로 설정하십시오.
위 코드의 라이브 예 (SBO 없음).
T의 인접 버퍼로 지우기
모든 타입 삭제가 가상 상속, 할당, 새로운 배치 또는 함수 포인터를 포함하는 것은 아닙니다.
타입 소거 타입 소거를 만드는 이유는 (일련의) 행동을 기술하고, 그 행동을 지원하고 그것을 감싸는 모든 타입을 취한다는 것입니다. 해당 행동 집합에 포함되지 않은 모든 정보는 "잊혀진"또는 "지워짐"입니다.
array_view
는 들어오는 범위 나 컨테이너 타입을 취해 T
의 인접한 버퍼라는 사실을 제외하고 모든 것을 지 웁니다.
// helper traits for SFINAE:
template<class T>
using data_t = decltype( std::declval<T>().data() );
template<class Src, class T>
using compatible_data = std::integral_constant<bool, std::is_same< data_t<Src>, T* >{} || std::is_same< data_t<Src>, std::remove_const_t<T>* >{}>;
template<class T>
struct array_view {
// the core of the class:
T* b=nullptr;
T* e=nullptr;
T* begin() const { return b; }
T* end() const { return e; }
// provide the expected methods of a good contiguous range:
T* data() const { return begin(); }
bool empty() const { return begin()==end(); }
std::size_t size() const { return end()-begin(); }
T& operator[](std::size_t i)const{ return begin()[i]; }
T& front()const{ return *begin(); }
T& back()const{ return *(end()-1); }
// useful helpers that let you generate other ranges from this one
// quickly and safely:
array_view without_front( std::size_t i=1 ) const {
i = (std::min)(i, size());
return {begin()+i, end()};
}
array_view without_back( std::size_t i=1 ) const {
i = (std::min)(i, size());
return {begin(), end()-i};
}
// array_view is plain old data, so default copy:
array_view(array_view const&)=default;
// generates a null, empty range:
array_view()=default;
// final constructor:
array_view(T* s, T* f):b(s),e(f) {}
// start and length is useful in my experience:
array_view(T* s, std::size_t length):array_view(s, s+length) {}
// SFINAE constructor that takes any .data() supporting container
// or other range in one fell swoop:
template<class Src,
std::enable_if_t< compatible_data<std::remove_reference_t<Src>&, T >{}, int>* =nullptr,
std::enable_if_t< !std::is_same<std::decay_t<Src>, array_view >{}, int>* =nullptr
>
array_view( Src&& src ):
array_view( src.data(), src.size() )
{}
// array constructor:
template<std::size_t N>
array_view( T(&arr)[N] ):array_view(arr, N) {}
// initializer list, allowing {} based:
template<class U,
std::enable_if_t< std::is_same<const U, T>{}, int>* =nullptr
>
array_view( std::initializer_list<U> il ):array_view(il.begin(), il.end()) {}
};
array_view
는 T
와 .size()
메소드 또는 배열에 대한 포인터를 리턴하는 .data()
를 지원하는 컨테이너를 취해 연속적인 T
에 대해 임의 액세스 범위로 지운다.
이것은 취할 수 std::vector<T>
하는 std::string<T>
std::array<T, N>
T[37]
, 이니셜 라이저 (체크리스트 {}
기초 것), 또는 어떤 다른 ( T* x.data()
및 size_t x.size()
) 지원합니다.
이 경우 우리가 지우는 것에서 추출 할 수있는 데이터는 우리의 "view"비 소유 상태와 함께 메모리를 할당하거나 사용자 정의 유형 종속 함수를 작성할 필요가 없다는 것을 의미합니다.
실례 예 .
ADL 사용 가능 컨텍스트에서 비 멤버 data
와 비 멤버 size
를 사용하는 것이 개선되었습니다.
std :: any를 사용하여 삭제 유형 삭제
이 예제는 C ++ 14와 boost::any
. C ++ 17에서는 std::any
대신 사용할 수 있습니다.
우리가 끝내는 구문은 다음과 같습니다.
const auto print =
make_any_method<void(std::ostream&)>([](auto&& p, std::ostream& t){ t << p << "\n"; });
super_any<decltype(print)> a = 7;
(a->*print)(std::cout);
거의 최적입니다.
이 예제는 @dyp 및 @cpplearner 의 작업뿐만 아니라 내 작업을 기반으로합니다.
먼저 태그를 사용하여 유형을 전달합니다.
template<class T>struct tag_t{constexpr tag_t(){};};
template<class T>constexpr tag_t<T> tag{};
이 특성 클래스는 any_method
저장된 서명을 가져 any_method
.
이것은 any_method
주어지면, 함수 포인터 타입과 상기 함수 포인터를위한 팩토리를 any_method
:
template<class any_method>
using any_sig_from_method = typename any_method::signature;
template<class any_method, class Sig=any_sig_from_method<any_method>>
struct any_method_function;
template<class any_method, class R, class...Args>
struct any_method_function<any_method, R(Args...)>
{
template<class T>
using decorate = std::conditional_t< any_method::is_const, T const, T >;
using any = decorate<boost::any>;
using type = R(*)(any&, any_method const*, Args&&...);
template<class T>
type operator()( tag_t<T> )const{
return +[](any& self, any_method const* method, Args&&...args) {
return (*method)( boost::any_cast<decorate<T>&>(self), decltype(args)(args)... );
};
}
};
any_method_function::type
은 인스턴스와 함께 저장할 함수 포인터의 유형입니다. any_method_function::operator()
는 tag_t<T>
any_method_function::type
의 사용자 정의 인스턴스를 작성합니다.이 인스턴스의 any&
가 T
가 될 것이라고 가정합니다.
우리는 한 번에 하나 이상의 방법을 입력 - 지울 수 있기를 원합니다. 그래서 우리는 그것들을 튜플에 묶고, 튜플을 유형별로 정적 저장소에 집어 넣고 포인터를 유지하도록 도우미 래퍼를 작성합니다.
template<class...any_methods>
using any_method_tuple = std::tuple< typename any_method_function<any_methods>::type... >;
template<class...any_methods, class T>
any_method_tuple<any_methods...> make_vtable( tag_t<T> ) {
return std::make_tuple(
any_method_function<any_methods>{}(tag<T>)...
);
}
template<class...methods>
struct any_methods {
private:
any_method_tuple<methods...> const* vtable = 0;
template<class T>
static any_method_tuple<methods...> const* get_vtable( tag_t<T> ) {
static const auto table = make_vtable<methods...>(tag<T>);
return &table;
}
public:
any_methods() = default;
template<class T>
any_methods( tag_t<T> ): vtable(get_vtable(tag<T>)) {}
any_methods& operator=(any_methods const&)=default;
template<class T>
void change_type( tag_t<T> ={} ) { vtable = get_vtable(tag<T>); }
template<class any_method>
auto get_invoker( tag_t<any_method> ={} ) const {
return std::get<typename any_method_function<any_method>::type>( *vtable );
}
};
vtable이 작은 경우 (예 : 1 개 항목)에이를 전문화 할 수 있으며 효율성을 위해 클래스에 저장된 직접 포인터를 사용합니다.
이제 우리는 super_any
시작합니다. 내가 사용 super_any_t
의 선언 할 super_any
좀 더 쉽게.
template<class...methods>
struct super_any_t;
SFINAE 및 더 나은 오류 메시지에 대해 슈퍼가 지원하는 메서드를 검색합니다.
template<class super_any, class method>
struct super_method_applies_helper : std::false_type {};
template<class M0, class...Methods, class method>
struct super_method_applies_helper<super_any_t<M0, Methods...>, method> :
std::integral_constant<bool, std::is_same<M0, method>{} || super_method_applies_helper<super_any_t<Methods...>, method>{}>
{};
template<class...methods, class method>
auto super_method_test( super_any_t<methods...> const&, tag_t<method> )
{
return std::integral_constant<bool, super_method_applies_helper< super_any_t<methods...>, method >{} && method::is_const >{};
}
template<class...methods, class method>
auto super_method_test( super_any_t<methods...>&, tag_t<method> )
{
return std::integral_constant<bool, super_method_applies_helper< super_any_t<methods...>, method >{} >{};
}
template<class super_any, class method>
struct super_method_applies:
decltype( super_method_test( std::declval<super_any>(), tag<method> ) )
{};
그런 다음 any_method
유형을 작성합니다. any_method
는 의사 메소드 포인터입니다. 우리는 다음과 같은 구문을 사용하여 전역으로 만들고 const
만듭니다.
const auto print=make_any_method( [](auto&&self, auto&&os){ os << self; } );
또는 C ++에서 17 :
const any_method print=[](auto&&self, auto&&os){ os << self; };
non-lambda를 사용하면 룩을 단계적으로 사용하기 때문에 털이 많은 것을 만들 수 있습니다. 이 문제는 해결할 수 있지만이 예제는 이미 존재하는 것보다 길게 만듭니다. 그래서 항상 람다 또는 람다에 parametarized 형식에서 모든 메서드를 초기화하십시오.
template<class Sig, bool const_method, class F>
struct any_method {
using signature=Sig;
enum{is_const=const_method};
private:
F f;
public:
template<class Any,
// SFINAE testing that one of the Anys's matches this type:
std::enable_if_t< super_method_applies< Any&&, any_method >{}, int>* =nullptr
>
friend auto operator->*( Any&& self, any_method const& m ) {
// we don't use the value of the any_method, because each any_method has
// a unique type (!) and we check that one of the auto*'s in the super_any
// already has a pointer to us. We then dispatch to the corresponding
// any_method_data...
return [&self, invoke = self.get_invoker(tag<any_method>), m](auto&&...args)->decltype(auto)
{
return invoke( decltype(self)(self), &m, decltype(args)(args)... );
};
}
any_method( F fin ):f(std::move(fin)) {}
template<class...Args>
decltype(auto) operator()(Args&&...args)const {
return f(std::forward<Args>(args)...);
}
};
C ++에서 필요하지 않은 팩토리 메서드 17 저는 믿습니다 :
template<class Sig, bool is_const=false, class F>
any_method<Sig, is_const, std::decay_t<F>>
make_any_method( F&& f ) {
return {std::forward<F>(f)};
}
이것은 any
증강 된 any
입니다. 그것은 둘 다 any
이며, 포함 된 any
가 변할 때마다 바뀌는 type-erasure 함수 포인터 묶음을 전달 any
.
template<class... methods>
struct super_any_t:boost::any, any_methods<methods...> {
using vtable=any_methods<methods...>;
public:
template<class T,
std::enable_if_t< !std::is_base_of<super_any_t, std::decay_t<T>>{}, int> =0
>
super_any_t( T&& t ):
boost::any( std::forward<T>(t) )
{
using dT=std::decay_t<T>;
this->change_type( tag<dT> );
}
boost::any& as_any()&{return *this;}
boost::any&& as_any()&&{return std::move(*this);}
boost::any const& as_any()const&{return *this;}
super_any_t()=default;
super_any_t(super_any_t&& o):
boost::any( std::move( o.as_any() ) ),
vtable(o)
{}
super_any_t(super_any_t const& o):
boost::any( o.as_any() ),
vtable(o)
{}
template<class S,
std::enable_if_t< std::is_same<std::decay_t<S>, super_any_t>{}, int> =0
>
super_any_t( S&& o ):
boost::any( std::forward<S>(o).as_any() ),
vtable(o)
{}
super_any_t& operator=(super_any_t&&)=default;
super_any_t& operator=(super_any_t const&)=default;
template<class T,
std::enable_if_t< !std::is_same<std::decay_t<T>, super_any_t>{}, int>* =nullptr
>
super_any_t& operator=( T&& t ) {
((boost::any&)*this) = std::forward<T>(t);
using dT=std::decay_t<T>;
this->change_type( tag<dT> );
return *this;
}
};
우리는 any_method
를 const
객체로 저장하기 때문에 이렇게하면 super_any
가 좀 더 쉽게 만들어집니다 :
template<class...Ts>
using super_any = super_any_t< std::remove_cv_t<Ts>... >;
테스트 코드 :
const auto print = make_any_method<void(std::ostream&)>([](auto&& p, std::ostream& t){ t << p << "\n"; });
const auto wprint = make_any_method<void(std::wostream&)>([](auto&& p, std::wostream& os ){ os << p << L"\n"; });
int main()
{
super_any<decltype(print), decltype(wprint)> a = 7;
super_any<decltype(print), decltype(wprint)> a2 = 7;
(a->*print)(std::cout);
(a->*wprint)(std::wcout);
}
라이브 예제 .
원래 SO 자체 질문 및 답변 (및 위에 언급 된 사람들은 구현에 도움이 됨)에 게시 되었습니다 .