patterncppMinor
Optional base class template to get conditional data members
Viewed 0 times
templatedataconditionaloptionalgetmembersclassbase
Problem
In generic code, I sometimes want to conditionally add a data member to a class template. Writing separate template specializations scales as
Exploiting the empty base optimization then allows users to write class templates that have optional data members (conditional on user-defined template variable traits
Note that the
Obvio
2^N for N conditional data members. Absent a static_if feature, I have found the following user-defined class templates empty_base and optional_base useful:namespace xstd {
namespace block_adl {
template
struct empty_base
{
empty_base() = default;
template
constexpr empty_base(Args&&...) {}
};
template
using optional_base = std::conditional_t>;
} // namespace block_adl
// deriving from empty_base or optional_base will not make xstd an associated namespace
// this prevents ADL from finding overloads for e.g. begin/end/swap from namespace xstd
using block_adl::empty_base;
using block_adl::optional_base;
} // namespace xstdExploiting the empty base optimization then allows users to write class templates that have optional data members (conditional on user-defined template variable traits
Trait1_v and Trait2_v)template
class Test
:
public Base0, // unconditional base class
public xstd::optional_base, Base1>,
public xstd::optional_base, Base2>
{
using B1 = xstd::optional_base, Base1>;
using B2 = xstd::optional_base, Base2>;
public:
Test() = default;
Test(Arg0 const& a0, Arg1 const& a1, Arg2 const& a2)
:
Base0(a0),
B1(a1),
B2(a2)
{}
};Note that the
optional_base variadic constructor allows linear scaling of constructor delegation in the user-defined class template Test. One minor wart is that this constructor does require passing all parameters (but otherwise, 2^N different constructors have to be written). But if any of the traits variables evaluates to false, the corresponding parameter and call to the base constructor will be optimized away. And because optional_base also has a trivial default constructor, POD-ness of user-defined classes is preserved by inheriting from it. Obvio
Solution
I only have a few comments. First, you probably want to inherit your bases privately:
After all, your design is about adding data members. That's something that you probably don't want to expose to the outside world.
Secondly, what happens if you want to potentially have multiple different members of the same type? That's definitely going to come up and so should be supported. In order to do that, you might want to add some kind of
This will additionally let you use non-class types as optional data members (since you couldn't inherit from, e.g.
That adds an inconvenience that you have to explicitly add the sequence 0, 1, ...
You can get around that by making a linear inheritance list of all your optional bases. I'm not sure if it's worth it, but I'll present the idea just as an option. At its core, we have
The root case is empty:
And we recurse over each pair of type trait/type:
Now we just have the top-level class to start us off at
which lets you write something like:
That's a lot of extra boilerplate for just avoiding writing a sequence of integers, so YMMV.
template
class Test
:
private Base0, // unconditional base class
private xstd::optional_base, Base1>,
private xstd::optional_base, Base2>
{ };After all, your design is about adding data members. That's something that you probably don't want to expose to the outside world.
Secondly, what happens if you want to potentially have multiple different members of the same type? That's definitely going to come up and so should be supported. In order to do that, you might want to add some kind of
ID to differentiate them and then additionally wrap the non-empty case. That is:template
struct empty_base { ... }; // same as before
template
struct wrapped {
T val;
};
template
using optional_base = std::conditional_t,
empty_base>;This will additionally let you use non-class types as optional data members (since you couldn't inherit from, e.g.
int). So you can now do something like:template
class Test
: private xstd::optional_base, int, 0>
, private xstd::optional_base, int, 1>
{ ... };That adds an inconvenience that you have to explicitly add the sequence 0, 1, ...
You can get around that by making a linear inheritance list of all your optional bases. I'm not sure if it's worth it, but I'll present the idea just as an option. At its core, we have
OptionalBasesImpl which will inherit from all the optional bases and provide a getter so that the derived class can actually use them:template
struct typelist { };
template
struct OptionalBasesImpl;The root case is empty:
template
struct OptionalBasesImpl
{
OptionalBasesImpl() = default;
template
OptionalBasesImpl(Args&&... ) { }
void get(typelist<> ) { }
};And we recurse over each pair of type trait/type:
template
struct OptionalBasesImpl
: private xstd::optional_base
, OptionalBasesImpl
{
using Base = xstd::optional_base;
using Root = OptionalBasesImpl;
OptionalBasesImpl() = default;
template
OptionalBasesImpl(Arg&& a0, Args&&... args)
: Base{std::forward(a0)}
, Root(std::forward(args)...)
{ }
protected:
using Root::get;
// by conditional/type pair
Base& get(typelist ) { return *this; }
Base const& get(typelist ) const { return *this; }
// by index
Base& get(std::integral_constant ) { return *this; }
Base const& get(std::integral_constant ) const { return *this; }
};Now we just have the top-level class to start us off at
0:template
struct OptionalBases : OptionalBasesImpl {
protected:
using OptionalBasesImpl::OptionalBasesImpl;
using OptionalBasesImpl::get;
};which lets you write something like:
template
struct Test
: private OptionalBases, int,
std::is_same, int>
{
using Bases = OptionalBases, int,
std::is_same, int>;
using Bases::Bases;
void printFirst() {
std::cout , int>{}).val {}).val t{1, 2};
t.printFirst();
t.printSecond();
}That's a lot of extra boilerplate for just avoiding writing a sequence of integers, so YMMV.
Code Snippets
template<class T>
class Test
:
private Base0, // unconditional base class
private xstd::optional_base<Trait1_v<T>, Base1>,
private xstd::optional_base<Trait2_v<T>, Base2>
{ };template <class, int >
struct empty_base { ... }; // same as before
template <class T, int >
struct wrapped {
T val;
};
template <bool Condition, class T, int UniqueID = 0>
using optional_base = std::conditional_t<Condition,
wrapped<T, UniqueID>,
empty_base<T, UniqueID>>;template <class T>
class Test
: private xstd::optional_base<Trait1_v<T>, int, 0>
, private xstd::optional_base<Trait2_v<T>, int, 1>
{ ... };template <typename... >
struct typelist { };
template <int, typename... >
struct OptionalBasesImpl;template <int ID>
struct OptionalBasesImpl<ID>
{
OptionalBasesImpl() = default;
template <typename... Args>
OptionalBasesImpl(Args&&... ) { }
void get(typelist<> ) { }
};Context
StackExchange Code Review Q#101541, answer score: 7
Revisions (0)
No revisions yet.