C++ Logo

std-proposals

Advanced search

[std-proposals] cout << any

From: Frederick Virchanza Gotham <cauldwell.thomas_at_[hidden]>
Date: Wed, 4 Jan 2023 15:55:39 +0000
It would be nice if we could output an 'std::any' to an 'std::ostream'
without having to use an 'std::any_cast". Specifically it would be
nice if we could do the following:

  int main(void)
  {
     std::any a;
     a = 5;
     cout << a << endl;
     a = std::string("dog");
     cout << a << endl;
     a = "fish";
     cout << a << endl;
     a = 5.2L;
     cout << a << endl;
     a = std::filesystem::temp_directory_path();
     cout << a << endl;
  }

I've written a new Standard header file called <any_ostream> which
makes the above code snippet possible. You can see it on GodBolt:

    https://godbolt.org/z/q11oYzqsc

I could go a step further and make it work with user-defined classes;
in order to get this to work there would have to be a global container
containing elements of type pair<
type_info*,function<void(ostream&,void*)> > -- and for this to work
properly in multithreaded programs then either:
    (a) The container must be populated from the main thread before
any other thread is spawned
    (b) Protect the container with a mutex

I have copy-pasted the code from GodBolt below:

#include <any> // any, any_cast
#include <type_traits> // is_same_v, is_const_v, remove_volatile_t,
remove_reference_t, remove_cvref_t, conditional_t
#include <ostream> // ostream

// The following 3 headers are required for the definitions
// of classes which can be outputted to an ostream
#include <complex> // complex<>
#include <thread> // thread::id
#include <filesystem> // filesystem::path
#include <string> // string
#include <string_view> // string_view

class anyer_success {};

template<typename T>
requires std::is_same_v< T, std::remove_volatile_t<
std::remove_reference_t<T> > > // make sure it's non-volatile non-ref
struct Proxy {
    Proxy(void) = delete;
    Proxy(Proxy const &) = delete;
    Proxy(Proxy &&) = delete;
    Proxy &operator=(Proxy const &) = delete;
    Proxy &operator=(Proxy &&) = delete;
    Proxy const volatile *operator&(void) const volatile = delete;
    template<typename U> void operator,(U&&) = delete;

    T *p; // might be a pointer to const

    Proxy(T *const arg) : p(arg) {} // argument might be a pointer to const
};

class AnyerNonConst {
    AnyerNonConst(void) = delete;
    AnyerNonConst(AnyerNonConst const &) = delete;
    AnyerNonConst(AnyerNonConst &&) = delete;
    AnyerNonConst &operator=(AnyerNonConst const &) = delete;
    AnyerNonConst &operator=(AnyerNonConst &&) = delete;
    AnyerNonConst const volatile *operator&(void) const volatile = delete;
    template<typename U> void operator,(U&&) = delete;

    std::any &a;

public:

    AnyerNonConst(std::any &arg) : a(arg) {}

    template<typename T>
    requires std::is_same_v< T, std::remove_cvref_t<T> > // make sure
it's non-const non-volatile non-ref
    Proxy<T> get(void)
    {
        return Proxy<T>( std::any_cast<T>( &this->a ) ); // return
value optimisation from C++17 onward
    }
};

class AnyerConst {
    AnyerConst(void) = delete;
    AnyerConst(AnyerConst const &) = delete;
    AnyerConst(AnyerConst &&) = delete;
    AnyerConst &operator=(AnyerConst const &) = delete;
    AnyerConst &operator=(AnyerConst &&) = delete;
    AnyerConst const volatile *operator&(void) const volatile = delete;
    template<typename U> void operator,(U&&) = delete;

    std::any const &a;

public:

    AnyerConst(std::any const &arg) : a(arg) {}

    template<typename T>
    requires std::is_same_v< T, std::remove_cvref_t<T> > // make sure
it's non-const non-volatile non-ref
    Proxy<T const> get(void) /* Note that this is not a const member
function (i.e. *this is non-const) */
    {
        return Proxy<T const>{ std::any_cast<T const>( &this->a ) };
    }
};

// The function on the next line gives us either an AnyerConst or AnyerNonConst
// depending on whether the argument is a ref-to-const or ref-to-nonconst
template<typename T>
requires std::is_same_v< T, std::remove_volatile_t<
std::remove_reference_t<T> > > // make sure it's non-volatile non-ref
inline std::conditional_t< std::is_const_v<T>, AnyerConst,
AnyerNonConst > Anyer(T &arg)
{
    return std::conditional_t< std::is_const_v<T>, AnyerConst,
AnyerNonConst >(arg); // return value optimisation from C++17 onward
}

template<typename T>
std::ostream &operator<<(std::ostream &os, Proxy<T> ap) // T might be
const here
{
    if ( nullptr == ap.p ) return os;

    os << *ap.p;

    throw anyer_success();
}

std::ostream &operator<<(std::ostream &os, std::any const &arg)
{
    auto a = Anyer(arg); // 'a' will be either an AnyerConst or an
AnyerNonConst

    try
    {
        os
        // First all the instrinsics
            << a.get<bool>()
            << a.get<int>()
            << a.get<char>()
            //<< a.get<char16_t>()
            //<< a.get<char32_t>()
            //<< a.get<char8_t>()
            << a.get<short>()
            << a.get<signed char>()
            << a.get<double>()
            << a.get<float>()
            << a.get<long>()
            << a.get<long double>()
            << a.get<long long>()
            << a.get<nullptr_t>()
            << a.get<unsigned char>()
            << a.get<unsigned int>()
            << a.get<unsigned long>()
            << a.get<unsigned long long>()
            << a.get<unsigned short>()
            //<< a.get<wchar_t>()

        // Now all the pointers
            << a.get<char const*>()
            //<< a.get<char16_t*>()
            //<< a.get<char32_t*>()
            //<< a.get<char8_t*>()
            << a.get<signed char const*>()
            << a.get<unsigned char const*>()
            << a.get<void const*>()
            //<< a.get<wchar_t*>()

        // Now the classes
            << a.get<std::complex<double>>()
            << a.get<std::complex<float>>()
            << a.get<std::complex<long double>>()
            << a.get<std::error_code>()
            << a.get<std::thread::id>()
            << a.get<std::filesystem::path>()
            //<< a.get< shared_ptr<> >() -- I'll code this later
            << a.get<std::string>()
            << a.get<std::string_view>()
            //<< a.get<std::wstring>()

        // And now the one's unsupported by GodBolt
        /*
            << a.get<std::address>()
            << a.get<std::address_v4>()
            << a.get<std::address_v6>()
            << a.get<std::annotate_base>()
            << a.get<std::annotate_base>()
            << a.get<std::bool_set>()
            << a.get<std::network_v4>()
            << a.get<std::network_v6>()
        */
            ;

    }
    catch(anyer_success) {}

    return os;
}

// = = = = = = = = Test code starts here = = = = = = = =

#include <iostream>
using std::cout;
using std::endl;

int main(void)
{
    std::any a;
    a = 5;
    cout << a << endl;
    a = std::string("dog");
    cout << a << endl;
    a = "fish";
    cout << a << endl;
    a = 5.2L;
    cout << a << endl;
    a = std::filesystem::temp_directory_path();
    cout << a << endl;
}

Received on 2023-01-04 15:55:52