Date: Tue, 6 Sep 2022 13:19:03 +0100
On Tue, 6 Sept 2022 at 13:02, Frederick Virchanza Gotham via Std-Proposals <
std-proposals_at_[hidden]> wrote:
> I program microcontrollers in my day job, so I'm weary of using
> dynamic memory allocation. I avoid it wherever possible.
>
> Currently I have an "interface class" to handle COM ports:
>
> class IRS232 {
> public:
> virtual void ConfigInputBuffer(char *const arg_buf,
> std::size_t const arg_buflen) = 0;
> virtual bool Open(char const *arg_str_portname, long
> unsigned arg_baud) = 0;
> virtual void Close(void) = 0;
> virtual std::size_t Read(void) = 0;
> virtual void Send(char const*,std::size_t) = 0;
>
> virtual ~IRS232(void) {}
> };
>
> I have two classes that derive from IRS232, like this:
>
> class RS232_Chunked : public IRS232 { . . . };
> class RS232_Instant : public IRS232 { . . . };
>
> So normally in a Desktop C++ program, if I wanted to decide at runtime
> what kind of RS232 processing I want, I would do:
>
> IRS232 *g_pcoms = nullptr;
>
> void Initialise_Comms(bool const chunked = false)
> {
> g_pcoms = chunked ? new RS232_Chunked : new RS232_Instant;
> }
>
> But because I'm on a microcontroller here, I don't want dynamic memory
> allocation. I want to use the same piece of memory for either an
> RS232_Chunked or an RS232_Instant. Before C++17, I would have used
> "std::aligned_union" for this purpose, but now instead we have
> "std::variant".
>
> So I can do the following:
>
> std::variant<std::nullopt_t,RS232_Chunked,RS232_Instant>
> g_coms( std::in_place_index<0u>, std::nullopt );
>
> IRS232 *g_pcoms = nullptr;
>
> void Initialise_Comms(bool const chunked = false)
> {
> if ( chunked )
> {
> g_coms.emplace<1u>();
> g_pcoms = &std::get<1u>(g_coms);
> }
> else
> {
> g_coms.emplace<2u>();
> g_pcoms = &std::get<2u>(g_coms);
> }
> }
>
> In order to simply this, I think that the "std::variant" class should
> have a method called "common_base", which could be used as follows:
>
Why would this need to be a member function? It can be written as a free
function using `std::visit`.
std::variant<std::nullopt_t,RS232_Chunked,RS232_Instant>
> g_coms( std::in_place_index<0u>, std::nullopt );
>
> IRS232 *g_pcoms = nullptr;
>
> void Initialise_Comms(bool const chunked = false)
> {
> if ( chunked ) g_coms.emplace<1u>();
> else g_coms.emplace<2u>();
>
> g_pcoms = g_coms.common_base<IRS232>();
> }
>
> The method, "common_base", would have the following compile-time
> characteristics/constraints:
> (1) If the first type in the variant is 'std::nullopt_t', and if the
> variant object currently hosts a 'std::nullopt_t', then return a
> nullptr
> (2) All other types in the variant must have T as a base class
>
The intended type to signal empty state for `std::variant` is
`std::monostate`, not `std::nullopt`:
template<class Base, std::derived_from<Base>... T>
Base* common_base(std::variant<std::monostate, T...>& v) {
return std::visit<Base*>([]<class U>(U& u) {
if constexpr (std::same_as<U, std::monostate>)
return nullptr;
else
return &u;
}), v);
}
std-proposals_at_[hidden]> wrote:
> I program microcontrollers in my day job, so I'm weary of using
> dynamic memory allocation. I avoid it wherever possible.
>
> Currently I have an "interface class" to handle COM ports:
>
> class IRS232 {
> public:
> virtual void ConfigInputBuffer(char *const arg_buf,
> std::size_t const arg_buflen) = 0;
> virtual bool Open(char const *arg_str_portname, long
> unsigned arg_baud) = 0;
> virtual void Close(void) = 0;
> virtual std::size_t Read(void) = 0;
> virtual void Send(char const*,std::size_t) = 0;
>
> virtual ~IRS232(void) {}
> };
>
> I have two classes that derive from IRS232, like this:
>
> class RS232_Chunked : public IRS232 { . . . };
> class RS232_Instant : public IRS232 { . . . };
>
> So normally in a Desktop C++ program, if I wanted to decide at runtime
> what kind of RS232 processing I want, I would do:
>
> IRS232 *g_pcoms = nullptr;
>
> void Initialise_Comms(bool const chunked = false)
> {
> g_pcoms = chunked ? new RS232_Chunked : new RS232_Instant;
> }
>
> But because I'm on a microcontroller here, I don't want dynamic memory
> allocation. I want to use the same piece of memory for either an
> RS232_Chunked or an RS232_Instant. Before C++17, I would have used
> "std::aligned_union" for this purpose, but now instead we have
> "std::variant".
>
> So I can do the following:
>
> std::variant<std::nullopt_t,RS232_Chunked,RS232_Instant>
> g_coms( std::in_place_index<0u>, std::nullopt );
>
> IRS232 *g_pcoms = nullptr;
>
> void Initialise_Comms(bool const chunked = false)
> {
> if ( chunked )
> {
> g_coms.emplace<1u>();
> g_pcoms = &std::get<1u>(g_coms);
> }
> else
> {
> g_coms.emplace<2u>();
> g_pcoms = &std::get<2u>(g_coms);
> }
> }
>
> In order to simply this, I think that the "std::variant" class should
> have a method called "common_base", which could be used as follows:
>
Why would this need to be a member function? It can be written as a free
function using `std::visit`.
std::variant<std::nullopt_t,RS232_Chunked,RS232_Instant>
> g_coms( std::in_place_index<0u>, std::nullopt );
>
> IRS232 *g_pcoms = nullptr;
>
> void Initialise_Comms(bool const chunked = false)
> {
> if ( chunked ) g_coms.emplace<1u>();
> else g_coms.emplace<2u>();
>
> g_pcoms = g_coms.common_base<IRS232>();
> }
>
> The method, "common_base", would have the following compile-time
> characteristics/constraints:
> (1) If the first type in the variant is 'std::nullopt_t', and if the
> variant object currently hosts a 'std::nullopt_t', then return a
> nullptr
> (2) All other types in the variant must have T as a base class
>
The intended type to signal empty state for `std::variant` is
`std::monostate`, not `std::nullopt`:
template<class Base, std::derived_from<Base>... T>
Base* common_base(std::variant<std::monostate, T...>& v) {
return std::visit<Base*>([]<class U>(U& u) {
if constexpr (std::same_as<U, std::monostate>)
return nullptr;
else
return &u;
}), v);
}
Received on 2022-09-06 12:19:16