I have a situation with a base class for whom a container of itself is a subclass. So a Block
of Item
has various ways in which it acts like an Item
itself. I want to be able to initialize Block
as Block {foo, baz, bar...}
in a variadic syntax.
Here are the twists:
When a
Block
is constructed, the parameters in the initialization list are not simply Items. (Imagine some represent two items... while others might just be a different meaning for the literal types passed in.) To simplify the example I am suggesting that constructing an Item from a C-string literal is not available to the user... but that a Block initialization list would know what to do with that in context.I'm trying to avoid creating
std::vector
in the course of the initialization, and instead building astd::array
for as near-zero runtime overhead as I can get. Each element in the Block initialization should be constructed (or copy constructed) just once in the process; right in place in the array... though this array is only needed temporarily. As far as I can tell, this rules outstd::initializer_list
and I must use a variadic function.
The basic idea is to create a class that has containership of an Item
which is a friend of Item... here called Listable
. It has its own set of constructors:
class Item {
friend class Listable;
friend class Block;
Item (char const *) { std::cout << "Istring\n"; }
Item (void *, size_t) { std::cout << "Iblock\n"; }
public:
Item (Item const &) { std::cout << "Icopy\n"; }
Item (float) { std::cout << "Ifloat\n"; }
};
class Listable {
Item item;
public:
Listable (Item const & i) : item (i) { std::cout << "Litem\n"; }
Listable (const char * s) : item (s) { std::cout << "Lstring\n"; }
Listable (float f) : item (f) { std::cout << "Lfloat\n"; }
};
class Block : public Item {
protected:
Block (Listable * l, size_t c) : Item (l, c) { std::cout << "Bpointer\n"; }
template<size_t N>
Block (std::array<Listable, N> a) : Block (&a[0], N) { std::cout << "Barray\n"; }
public:
template<typename... Ts>
Block (Ts const & ...t) : Block (std::array<Listable, sizeof...(t)>{t...})
{ std::cout << "B\n"; }
};
Here is a usage, showing a string not working for the construction of an Item while the Block knows how to handle it. The goodItem is the only one that needs to be copied, as its original instance was not constructed in-place in an array slot:
#include <iostream>
#include <array>
/* #include the classes above */
int main() {
auto goodItem = Item {10.20};
auto goodBlock = Block {goodItem, "blue", "red", 3.04};
/* auto misplacedItem = Item {"purple"}; */ // errors, correctly...
}
I compiled with no warnings with:
g++ -pedantic -Wall -Wsign-conversion -Wextra -Wcast-align -Wcast-qual -Wctor-dtor-privacy -Wdisabled-optimization -Wformat=2 -Winit-self -Wlogical-op -Wmissing-declarations -Wmissing-include-dirs -Wnoexcept -Wold-style-cast -Woverloaded-virtual -Wredundant-decls -Wsign-promo -Wstrict-null-sentinel -Wstrict-overflow=5 -Wswitch-default -Wundef -Werror -Wno-unused --std=c++11 test.cpp -o test
This output is what I wanted to see:
Ifloat Icopy Litem Istring Lstring Istring Lstring Ifloat Lfloat Iblock Bpointer Barray B
Can anyone spots any problems or suggestions for improvement on this technique?