I want to have a nested class B
that behaves as follows:
if T
is void
, then B
has only one member field sz
and sizeof(B) == sizeof(sz)
otherwise, B
has a member field sz
and somehow obtains T
#include <iostream>
#include <type_traits>
template <typename T>
class A {
struct B {
size_t sz;
if (T != void)
T t; // declare T field
};
};
I have found this partial solution but it works for integral types only and has some inconveniences in accessing T
data.
static constexpr bool is_void = std::is_same_v<void, T>;
using type = std::conditional_t<is_void, bool, T>;
struct B {
size_t sz;
type : (is_void ? 0 : sizeof(type));
// if 'type' has a name, possible CE: Named bit-field has zero width
};
Alternatively, we can create something similar with the inheritance trick but I want to find other ways (no boilerplate code) the language provides (~specifically created for such cases).
struct empty_class {};
struct container { T t; }; // solves (std::is_class_v<T> && !std::is_final_v<T>)
static constexpr bool is_void = std::is_same_v<void, T>;
using base = std::conditional_t<is_void, empty_class, container>;
struct B : base {
size_t sz;
};
Potentially, the specialization of A<void>
can create a lot of boilerplate code.
For clarity: I want universe std::__detail::__extent_storage
that std::span
has (it stores everything, except for void
)
Even when a specialization of all of A
isn't a viable option, you can specialize just the part that absolutely needs it.
Here, the only difference between the two cases is whether T t
exists or not.
template <typename T>
struct maybe {
T t;
};
template <>
struct maybe<void> { };
template <typename T>
class A {
struct B : maybe<T> {
std::size_t sz;
};
};
Having a base class of type maybe<void>
should not add anything to the size of B
because of empty base class optimization.
std::conditional_t
struct empty { };
template <typename T>
class A {
struct B {
std::size_t sz;
[[no_unique_address]] std::conditional_t<std::is_same_v<T, void>, empty, T> t;
};
};
This approach should also not cost any size increase if t
is of type empty
, however, it pollutes B
with a possibly unwanted empty t
member.
B
template <typename T>
class A {
struct B {
std::size_t sz;
T t;
};
};
template <>
struct A<void>::B {
std::size_t sz;
};
This approach wouldn't require specializing all of A
, but all of B
.
It may be the most redundant in a larger example, but it is the cleanest and simplest.