C++
Tipo de borrado
Buscar..
Introducción
El borrado de tipo es un conjunto de técnicas para crear un tipo que puede proporcionar una interfaz uniforme a varios tipos subyacentes, mientras oculta la información de tipo subyacente del cliente. std::function<R(A...)>
, que tiene la capacidad de contener objetos de diferentes tipos, es quizás el mejor ejemplo conocido de borrado de tipo en C ++.
Mecanismo basico
El borrado de tipo es una forma de ocultar el tipo de un objeto del código que lo usa, aunque no se derive de una clase base común. Al hacerlo, proporciona un puente entre los mundos del polimorfismo estático (plantillas; en el lugar de uso, el tipo exacto se debe conocer en el momento de la compilación, pero no es necesario declararlo para que se ajuste a una interfaz en la definición) y el polimorfismo dinámico. (herencia y funciones virtuales; en el lugar de uso, no es necesario conocer el tipo exacto en el momento de la compilación, pero debe declararse que se ajusta a una interfaz en la definición).
El siguiente código muestra el mecanismo básico de borrado de tipo.
#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;
};
En el sitio de uso, solo la definición anterior debe estar visible, al igual que con las clases base con funciones virtuales. Por ejemplo:
#include <iostream>
void print_value(Printable const &p)
{
p.print(std::cout);
}
Tenga en cuenta que esto no es una plantilla, sino una función normal que solo se debe declarar en un archivo de encabezado y se puede definir en un archivo de implementación (a diferencia de las plantillas, cuya definición debe ser visible en el lugar de uso).
En la definición del tipo concreto, no hay que saber nada acerca de Printable
, solo debe ajustarse a una interfaz, al igual que con las plantillas:
struct MyType { int i; };
ostream& operator << (ostream &os, MyType const &mc)
{
return os << "MyType {" << mc.i << "}";
}
Ahora podemos pasar un objeto de esta clase a la función definida anteriormente:
MyType foo = { 42 };
print_value(foo);
Borrado a un tipo regular con vtable manual
C ++ prospera en lo que se conoce como un tipo Regular (o al menos Pseudo-Regular).
Un tipo Regular es un tipo que se puede construir y asignar y asignar a través de copiar o mover, se puede destruir y se puede comparar igual a. También se puede construir a partir de ningún argumento. Finalmente, también tiene soporte para algunas otras operaciones que son muy útiles en varios algoritmos y contenedores std
.
Este es el documento raíz , pero en C ++ 11 querría agregar compatibilidad con std::hash
.
Voy a utilizar el enfoque manual de vtable para el borrado de tipo aquí.
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);
}
};
}
Dicho tipo regular se puede usar como una clave para un std::map
o un std::unordered_map
que acepta cualquier regular para una clave, como:
std::map<regular_type, std::any>
Básicamente sería un mapa desde algo normal, a cualquier cosa copiable.
A diferencia de any
, mi tipo de regular_type
no hace optimización de objetos pequeños ni admite recuperar los datos originales. Recuperar el tipo original no es difícil.
La optimización de objetos pequeños requiere que almacenemos un búfer de almacenamiento alineado dentro del tipo regular_type
, y que regular_type
cuidadosamente el eliminador de ptr
para que solo destruya el objeto y no lo elimine.
Comenzaría en make_dtor_unique_ptr
y le enseñaría cómo almacenar los datos a veces en un búfer, y luego en el montón si no hay espacio en el búfer. Eso puede ser suficiente.
Una función `std :: function` solo para movimiento
std::function
tipo de std::function
borra hasta unas pocas operaciones. Una de las cosas que requiere es que el valor almacenado sea copiable.
Esto causa problemas en algunos contextos, como lambdas que almacenan ptrs únicos. Si está utilizando la std::function
en un contexto en el que no importa la copia, como un grupo de subprocesos donde se envían tareas a subprocesos, este requisito puede agregar una sobrecarga.
En particular, std::packaged_task<Sig>
es un objeto que se puede llamar y que solo se puede mover. Puede almacenar std::packaged_task<R(Args...)>
en std::packaged_task<void(Args...)>
, pero es una forma bastante pesada y oscura de crear un movimiento solo Clase de borrado de tipos de llamada.
De task
la task
. Esto demuestra cómo se puede escribir un tipo de std::function
simple. Omití el constructor de copia (lo que implicaría agregar un método de clone
a los details::task_pimpl<...>
también).
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());
}
};
Para hacer que esta biblioteca valga la pena, querría agregar una pequeña optimización de búfer, para que no almacene todos los invocables en el montón.
Agregar SBO requeriría una task(task&&)
no predeterminada task(task&&)
, algo de std::aligned_storage_t
dentro de la clase, un m_pImpl
unique_ptr
con un eliminador que se puede configurar para destruir solo (y no devolver la memoria al montón), y un emplace_move_to( void* ) = 0
en el task_pimpl
.
Ejemplo vivo del código anterior (sin SBO).
Borrado hasta un búfer contiguo de T
No todo el borrado de tipos implica herencia virtual, asignaciones, ubicación nueva o incluso punteros a funciones.
Lo que hace que se borre el tipo de borrado de tipo es que describe un (conjunto de) comportamiento (s), y toma cualquier tipo que admita ese comportamiento y lo envuelve. Toda la información que no está en ese conjunto de comportamientos es "olvidada" o "borrada".
Una array_view
toma su rango entrante o tipo de contenedor y borra todo excepto el hecho de que es un búfer contiguo de 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()) {}
};
una array_view
toma cualquier contenedor que admita .data()
devolviendo un puntero a T
y un método .size()
, o una matriz, y lo borra a un rango de acceso aleatorio sobre T
s contiguos.
Puede tomar un std::vector<T>
, un std::string<T>
un std::array<T, N>
a T[37]
, una lista de inicializadores (incluidos los basados en {}
), o algo más Usted crea que lo admite (a través de T* x.data()
y size_t x.size()
).
En este caso, los datos que podemos extraer de la cosa que estamos borrando, junto con nuestro estado no propietario "vista", significa que no tenemos que asignar memoria o escribir funciones personalizadas dependientes de los tipos.
Una mejora sería utilizar data
no miembros y un size
no miembros en un contexto habilitado para ADL.
Borrado de tipos Borrado de tipos con std :: any
Este ejemplo usa C ++ 14 y boost::any
. En C ++ 17 puedes intercambiar en std::any
lugar.
La sintaxis con la que terminamos es:
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);
que es casi óptimo
Este ejemplo se basa en el trabajo de @dyp y @cpplearner , así como el mío.
Primero usamos una etiqueta para pasar tipos:
template<class T>struct tag_t{constexpr tag_t(){};};
template<class T>constexpr tag_t<T> tag{};
Esta clase de rasgo obtiene la firma almacenada con any_method
:
Esto crea un tipo de puntero de función y una fábrica para dichos punteros de función, dado un 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
es el tipo de puntero a una función que almacenaremos junto con la instancia. any_method_function::operator()
toma un tag_t<T>
y escribe una instancia personalizada del any_method_function::type
que asume que any&
va a ser una T
Queremos poder escribir más de un método a la vez. Así que los agrupamos en una tupla, y escribimos un envoltorio de ayuda para pegar la tupla en el almacenamiento estático por tipo y mantener un puntero a ellos.
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 );
}
};
Podríamos especializarlo en casos en los que el vtable es pequeño (por ejemplo, 1 elemento), y usar punteros directos almacenados en clase en esos casos por eficiencia.
Ahora empezamos la super_any
. Utilizo super_any_t
para hacer la declaración de super_any
un poco más fácil.
template<class...methods>
struct super_any_t;
Esto busca los métodos que Super Super admite para SFINAE y mejores mensajes de error:
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> ) )
{};
A continuación creamos el tipo any_method
. Un any_method
es un pseudo-método-puntero. Lo creamos globalmente y const
usando sintaxis como:
const auto print=make_any_method( [](auto&&self, auto&&os){ os << self; } );
o en C ++ 17:
const any_method print=[](auto&&self, auto&&os){ os << self; };
Tenga en cuenta que usar un dispositivo no lambda puede hacer que las cosas se pongan peludas, ya que usamos el tipo para un paso de búsqueda. Esto se puede arreglar, pero haría este ejemplo más largo de lo que ya es. Por lo tanto, siempre inicialice cualquier método desde un lambda, o desde un tipo parametarizado en un lambda.
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)...);
}
};
Un método de fábrica, no es necesario en C ++ 17, creo:
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)};
}
Este es el aumento de any
. Es a la vez una any
, y lleva en torno a un conjunto de punteros a funciones de tipo de borrado que cambian cada vez que el contenido any
lo hace:
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;
}
};
Debido a que almacenamos los any_method
s como objetos const
, esto hace que hacer un super_any
un poco más fácil:
template<class...Ts>
using super_any = super_any_t< std::remove_cv_t<Ts>... >;
Código de prueba:
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);
}
Originalmente publicado aquí en una auto-pregunta y respuesta de SO (y las personas mencionadas anteriormente ayudaron con la implementación).