In generic code, I sometimes want to conditionally add a data member to a class template. Writing separate template specializations scales as 2^N
for N
conditional data members. Absent a static_if feature, I have found the following user-defined class templates empty_base<T>
and optional_base<T>
useful:
namespace xstd {
namespace block_adl {
template<class>
struct empty_base
{
empty_base() = default;
template<class... Args>
constexpr empty_base(Args&&...) {}
};
template<bool Condition, class T>
using optional_base = std::conditional_t<Condition, T, empty_base<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 xstd
Exploiting 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 T>
class Test
:
public Base0, // unconditional base class
public xstd::optional_base<Trait1_v<T>, Base1>,
public xstd::optional_base<Trait2_v<T>, Base2>
{
using B1 = xstd::optional_base<Trait1_v<T>, Base1>;
using B2 = xstd::optional_base<Trait2_v<T>, 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.
Obviously, over-use of inheritance has to be avoided, but the above design does avoid quite a lot of code duplication. Oh, and I haven't tested this with virtual functions, virtual base classes and what-not. I just use this for assembling value-semantic classes out of conditional building blocks.
Question: what other design / usability issues could there be with the above code?
template<class T> constexpr auto Trait1_v = std::is_integral<T>::value;
(in C++14 you can have variable templates) \$\endgroup\$