I have the following class that is used to keep track of objects upon creation. These are all objects at file scope with static storage.
template <typename derived, std::size_t size = std::numeric_limits<std::size_t>::max()>
class ObjectTracker{
private:
ObjectTracker(ObjectTracker const &) = delete;
ObjectTracker& operator=(ObjectTracker const &) = delete;
static std::size_t total;
static std::vector<derived*> objects;
std::size_t id;
protected:
ObjectTracker(){
if(total < size){
objects.push_back(static_cast<derived*>(this));
id = total++;
}
else throw std::length_error("Too many objects are being created in ObjectTracker subclass.");
}
public:
static derived* getNth(std::size_t n) {return (n < getList().size()) ? getList()[n] : nullptr;}
static std::size_t getCount() {return total;}
static std::vector<derived*> const & getList() {return objects;}
std::size_t getID() const {return id;}
};
template <typename derived, std::size_t size> std::size_t ObjectTracker<derived, size>::total;
template <typename derived, std::size_t size> std::vector<derived*> ObjectTracker<derived, size>::objects;
I use it like the following
class Auton: public ObjectTracker<Auton>{
//Other stuff
public:
Auton(std::string name, std::function<void()> program, E_Reset_Types reset_type = E_Reset_Types::home){
//saves vars
};
If I check getList() during the creation of objects, I can see it correctly filled up. It has the elements in the order of creation, and is the right size.
However in main() calling getList().size() returns 0. getCount() still returns the correct value, but the vector seems to get reset.
This behaviour is inconsistent. I have 4 different classes that derive from ObjectTracker. 2 of them work as expected, and the other two exhibit the resetting vector.
Is there any explanation for why the vector gets reset?
If it helps, this works just fine on my computer, running clang++20. However the issue occurs when running on a VEX V5 in the PROS environment which compiles using gnu++20.
Related
I have a base class which writes objects to a std::ostream with buffering. I want to call this with
Obj obj;
flat_file_stream_writer<Obj> writer(std::cout);
writer.write(obj);
writer.flush();
but also with
Obj obj;
flat_file_writer<Obj> writer("filename.bin");
writer.write(obj);
writer.flush();
The latter call must establish a std::ofstream instance, and hold that for the duration of writing, and can then call the former version.
I thought simple non-virtual inheritance would be fine here. But I have constructor initialization order issues.
warning: field 'ofstream_' will be initialized after base
'flat_file_stream_writer<hibp::pawned_pw>' [-Wreorder-ctor]
Because the base class depends on a reference to the object held by the derived this seems like chicken and egg and can't be solved?
Is inheritance just the wrong abstraction here? A clean alternative?
template <typename ValueType>
class flat_file_stream_writer {
public:
explicit flat_file_stream_writer(std::ostream& os, std::size_t buf_size = 100)
: db_(os), buf_(buf_size) {}
void write(const ValueType& value) {
if (buf_pos_ == buf_.size()) flush();
std::memcpy(&buf_[buf_pos_], &value, sizeof(ValueType));
++buf_pos_;
}
void flush() { // could also be called in destructor?
if (buf_pos_ != 0) {
db_.write(reinterpret_cast<char*>(buf_.data()), // NOLINT reincast
static_cast<std::streamsize>(sizeof(ValueType) * buf_pos_));
buf_pos_ = 0;
}
}
private:
std::ostream& db_;
std::size_t buf_pos_ = 0;
std::vector<ValueType> buf_;
};
template <typename ValueType>
class flat_file_writer : public flat_file_stream_writer<ValueType> {
public:
explicit flat_file_writer(std::string dbfilename)
: dbfilename_(std::move(dbfilename)), dbpath_(dbfilename_),
ofstream_(dbpath_, std::ios::binary), flat_file_stream_writer<ValueType>(ofstream_) {
if (!ofstream_.is_open())
throw std::domain_error("cannot open db: " + std::string(dbpath_));
}
private:
std::string dbfilename_;
std::filesystem::path dbpath_;
std::ofstream ofstream_;
};
You can put std::ofstream ofstream_; in a separate struct, then have flat_file_writer inherit from that struct using private. Base classes are initialized in the order they are declared, so make sure to inherit from that struct before flat_file_stream_writer. You can now initialize that ofstream_ before the base class flat_file_stream_writer.
On closer inspection, I think you'll probably want to put all 3 of those members in the struct.
See below for updated code.
So this in effect means that the 3 members in ofstream_holder now come before the flat_file_stream_writer in the layout of flat_file_writer.
This seems like the right solution, not just because it "squashes the compiler warning", but because now we are really getting the correct initialization order, ie construct the std::ofstream first and then pass a reference to it to flat_file_stream_writer. And during destruct the std::ofstream will be destroyed last.
The original code above had this exactly the wrong way around, because the order we write in the derived constructor initializer is ignored and the actual order implemented is layout order, ie the order the members are declared.
(despite the flaws of the original code, it "ran fine" for me with sanitizers etc... but was probably UB?).
template <typename ValueType>
class flat_file_stream_writer {
public:
explicit flat_file_stream_writer(std::ostream& os, std::size_t buf_size = 100)
: db_(os), buf_(buf_size) {}
void write(const ValueType& value) {
if (buf_pos_ == buf_.size()) flush();
std::memcpy(&buf_[buf_pos_], &value, sizeof(ValueType));
++buf_pos_;
}
void flush() {
if (buf_pos_ != 0) {
db_.write(reinterpret_cast<char*>(buf_.data()), // NOLINT reincast
static_cast<std::streamsize>(sizeof(ValueType) * buf_pos_));
buf_pos_ = 0;
}
}
private:
std::ostream& db_;
std::size_t buf_pos_ = 0;
std::vector<ValueType> buf_;
};
struct ofstream_holder {
explicit ofstream_holder(std::string dbfilename)
: dbfilename_(std::move(dbfilename)), dbpath_(dbfilename_),
ofstream_(dbpath_, std::ios::binary) {}
std::string dbfilename_;
std::filesystem::path dbpath_;
std::ofstream ofstream_;
};
template <typename ValueType>
class flat_file_writer : private ofstream_holder, public flat_file_stream_writer<ValueType> {
public:
explicit flat_file_writer(std::string dbfilename)
: ofstream_holder(std::move(dbfilename)), flat_file_stream_writer<ValueType>(ofstream_) {
if (!ofstream_.is_open())
throw std::domain_error("cannot open db: " + std::string(dbpath_));
}
};
So guys, I have an abstract class, other class that stores an implementation from this class in the stack (I don't want heap allocations and I don't know other way to do it without making the caller explicitly declares the implementation) and another that stores a reference of this interface class. But, it seems that GCC don't store the implementation class in the stack and when the interface class is used probably the implementation class vtable is not found.
Basically, everything works fine when compiled with GCC 4.8.1 without optimizations, but when I try to use it, the program crashes and then returns 139.
I don't know why GCC 4 doesn't support it, while GCC 5 does, but I see that they generate different instructions.
Compiler Explorer: https://godbolt.org/z/Wfvj65
#include <cstdio>
#define FORCEINLINE inline __attribute__((always_inline))
class IFormatter
{
public:
virtual void Format(const void* InData) const = 0;
};
template<typename T>
class TFormatter :
public IFormatter
{
public:
TFormatter() = delete;
};
using Scalar = float;
// Implemented, fine.
struct RVector2
{
Scalar X;
Scalar Y;
};
// Not implemented, get error.
struct RVector3
{
Scalar X;
Scalar Y;
Scalar Z;
};
template<>
class TFormatter<RVector2> :
public IFormatter
{
public:
virtual void Format(const void*) const override
{
printf("[RVector2]\n");
}
};
template<typename T>
class TCustom
{
public:
FORCEINLINE TCustom(const T& InValue) :
Value(InValue),
Format(TFormatter<T>{})
{
}
FORCEINLINE const T* Data() const
{
return &Value;
}
FORCEINLINE const IFormatter& Formatter() const
{
return Format;
}
private:
const T& Value;
TFormatter<T> Format;
};
template<typename T>
FORCEINLINE TCustom<T> MakeCustom(const T& InValue)
{
return TCustom<T>{ InValue };
}
class RCustom
{
public:
FORCEINLINE RCustom(const void* InValue, const IFormatter& InFormatter) :
Data(InValue),
Formatter(InFormatter)
{
}
template<typename T>
FORCEINLINE RCustom(TCustom<T> const& InCustom) :
RCustom(InCustom.Data(), InCustom.Formatter())
{
}
FORCEINLINE const IFormatter& Get() const
{
return Formatter;
}
private:
const void* Data;
const IFormatter& Formatter;
};
int main()
{
const RVector2 Vector{};
const RCustom Custom = MakeCustom(Vector);
Custom.Get().Format(nullptr);
return 0;
}
As one of the comments said there is something weird going on with storing TCustom in the unrelated type RCustom. The implicit constructor RCustom(TCustom) threw me off.
The issue is quite subtle. If something works with -O0 but not with -Ofast (or -O2/-O3), most of the time something funny is happening with the memory. As Benny K said, in your case the issue is that RCustom only stores a reference to IFormatter:
class RCustom {
...
const IFormatter& Formatter; // Asking for problems
}
This is seems like an innocent &, but in fact this is dangerous. Because the validity of this member is dependent on the lifetime of an external object. There are a few possibilities to fix this. You could save a copy of the TFormatter in RCustom (instead of a reference):
template<typename T>
class RCustom {
...
const TFormatter<T> Formatter;
}
But this also means you have to give up the abstract interface IFormatter for the concrete one TFormatter<T>. To work with virtual methods in C++ you need a pointer, but using a raw-pointer will introduce the same memory problems as the references. So I suggest you use smart pointers:
class RCustom {
...
std::shared_ptr<const IFormatter> Formatter;
}
PS: to be precise about what's going wrong: In MakeCustom() you initialize a TCustom object which initializes and copies an instance of TFormatter. Next a reference to the instance of TFormatter in TCustom is saved in RCustom. Now this RCustom object is returned and the function MakeCustom() is cleaned up. In this cleaning process TCustom is destroyed, and so is the TFormatter-member. But the RCustom still retains a reference to this invalid memory. In C++ the difference between an & and no & is rather important.
I'm implementing a graph class, with each vertex having a Label of not necessarily the same type. I want the user to be able to provide any Labels (at compile time), without the Graph or the Vertex to know what the type is. For this, I used templated polymorphism, which I've hidden inside a Label class, in order for the Labels to have value semantics. It works like a charm and the relevant code is this (ignore the commented parts for now):
//Label.hpp:
#include <memory>
class Label {
public:
template<class T> Label(const T& name) : m_pName(new Name<T>(name)) {}
Label(const Label& other) : m_pName(other.m_pName->copy()) {}
// Label(const Label& other, size_t extraInfo) : m_pName(other.m_pName->copyAndAddInfo(extraInfo)) {}
bool operator==(const Label& other) const { return *m_pName == *other.m_pName; }
private:
struct NameBase {
public:
virtual ~NameBase() = default;
virtual NameBase* copy() const = 0;
// virtual NameBase* copyAndAddInfo(size_t info) const = 0;
virtual bool operator==(const NameBase& other) const = 0;
};
template<class T> struct Name : NameBase {
public:
Name(T name) : m_name(std::move(name)) {}
NameBase* copy() const override { return new Name<T>(m_name); }
// NameBase* copyAndAddInfo(size_t info) const override {
// return new Name<std::pair<T, size_t>>(std::make_pair(m_name, info));
// }
bool operator==(const NameBase& other) const override {
const auto pOtherCasted = dynamic_cast<const Name<T>*>(&other);
if(pOtherCasted == nullptr) return false;
return m_name == pOtherCasted->m_name;
}
private:
T m_name;
};
std::unique_ptr<NameBase> m_pName;
};
One requirement of the user (aka me) is to be able to create disjoint unions of Graphs (he is already able to create dual Graphs, unions of Graphs (where vertices having the same Label, are mapped to the same vertex), etc.). The wish is that the labels of the new Graph are pairs of the old label and some integer, denoting from which graph the label came (this also ensures that the new labels are all different). For this, I thought that I could use the commented parts of the Label class, but the problem that my g++17 compiler has, is that the moment I define the first Label with some type T, it tries to instantiate everything that could be used:
Name<T>, Name<std::pair<T, size_t>>, Name<std::pair<std::pair<T, size_t>, size_t>>, ...
Try for example to compile this (just an example, that otherwise works):
// testLabel.cpp:
#include "Label.hpp"
#include <vector>
#include <iostream>
int main() {
std::vector<Label> labels;
labels.emplace_back(5);
labels.emplace_back(2.1);
labels.emplace_back(std::make_pair(true, 2));
Label testLabel(std::make_pair(true, 2));
for(const auto& label : labels)
std::cout<<(label == testLabel)<<std::endl;
return 0;
}
The compilation just freezes. (I do not get the message "maximum template recursion capacity exceeded", that I saw others get, but it obviously tries to instantiate everything). I've tried to separate the function in another class and explicitly initialize only the needed templates, in order to trick the compiler, but with no effect.
The desired behaviour (I do not know if possible), is to instantiate the used template classes (together with the member function declarations), but define the member functions lazily, i.e. only if they really get called. For example, if I call Label(3), there should be a class Name<int>, but the function
NameBase* Name<int>::copyAndAddInfo(size_t info) const;
shall only be defined if I call it, at some point. (thus, the Name<std::pair<int, size_t>> is only going to be instantiated on demand)
It feels like something which should be doable, since the compiler already defines templated functions on demand.
An idea whould be to completely change the implementation and use variants, but
I do not want to keep track of the types the user needs manually, and
I quite like this implementation approach and want to see its limits, before changing it.
Does anyone have any hints on how I could solve this problem?
To directly answer your question, the virtual and template combo makes it impossible for the compiler to lazily implement the body copyAndAddInfo. The virtual base type pointer hides the type information, so when the compiler sees other.m_pName->copyAndAddInfo, it couldn't know what type it needs to lazily implement.
EDIT:
Ok, so based on your rationale for using templates, it seems like you only want to accept labels of different types, and might not actually care if the disjoint union information is part of the type. If that's the case, you could move it from the name to the label, and make it run-time information:
class Label {
public:
template<class T> Label(const T& name) : m_pName(new Name<T>(name)) {}
Label(const Label& other) : m_pName(other.m_pName->copy()), m_extraInfo(other.m_extraInfo) { }
Label(const Label& other, size_t extraInfo) : m_pName(other.m_pName->copy()), m_extraInfo(other.m_extraInfo) {
m_extraInfo.push_back(extraInfo);
}
bool operator==(const Label& other) const {
return *m_pName == *other.m_pName && std::equal(
m_extraInfo.begin(), m_extraInfo.end(),
other.m_extraInfo.begin(), other.m_extraInfo.end()); }
private:
struct NameBase { /* same as before */ };
std::vector<size_t> m_extraInfo;
std::unique_ptr<NameBase> m_pName;
};
If the disjoint union info being part of the type is important, than please enjoy my original sarcastic answer below.
ORIGINAL ANSWER:
That said, if you're willing to put a cap on the recursion, I have an evil solution for you that works for up to N levels of nesting: use template tricks to count the level of nesting. Then use SFINAE to throw an error after N levels, instead of recursing forever.
First, to count the levels of nesting:
template <typename T, size_t Level>
struct CountNestedPairsImpl
{
static constexpr size_t value = Level;
};
template <typename T, size_t Level>
struct CountNestedPairsImpl<std::pair<T, size_t>, Level> : CountNestedPairsImpl<T, Level + 1>
{
using CountNestedPairsImpl<T, Level + 1>::value;
};
template <typename T>
using CountNestedPairs = CountNestedPairsImpl<T, 0>;
Then, use std::enable_if<> to generate different bodies based on the nesting level:
constexpr size_t NESTING_LIMIT = 4;
NameBase* copyAndAddInfo(size_t info) const override {
return copyAndAddInfoImpl(info);
}
template <typename U = T, typename std::enable_if<CountNestedPairs<U>::value < NESTING_LIMIT, nullptr_t>::type = nullptr>
NameBase* copyAndAddInfoImpl(size_t info) const {
return new Name<std::pair<T, size_t>>(std::make_pair(m_name, info));
}
template <typename U = T, typename std::enable_if<CountNestedPairs<U>::value >= NESTING_LIMIT, nullptr_t>::type = nullptr>
NameBase* copyAndAddInfoImpl(size_t info) const {
throw std::runtime_error("too much disjoint union nesting");
}
Why did I call this evil? It's going to generate every possible level of nesting allowed, so if you use NESTING_LIMIT=20 it will generate 20 classes per label type. But hey, at least it compiles!
https://godbolt.org/z/eaQTzB
I'm implementing a Big Integer library where the user can choose between fixed precision or arbitrary precision integers. Since great part of the code is shared between the two entities I've decided to use the CRTP to implement the Integer operations just once.
In short there is a base class named UInteger and two derived classes named UIntegerFP (fixed precision) and UIntegerAP (arbitrary precision).
Follows a skeleton of the implementation:
template <typename Derived>
class UInteger
{
public:
UInteger<Derived> &operator +=(const UInteger<Derived> &rhs);
...
};
template <int blocks>
class UIntegerFP : public UInteger<UIntegerFP>
{
public:
int get_size() { return m_len; }
void set_size(int size) { m_len = len; }
private:
std::array<uint32_t, blocks> m_data;
int m_len;
};
class UIntegerAP : public UInteger<UIntegerAP>
{
public:
int get_size() { return m_data.size(); }
void set_size(int size) { m_data.resize(len); }
private:
std::vector<uint32_t> m_data;
};
The base class uses a couple of methods exposed by the derived classes to interact with implementation dependent aspects (ie like get_size/set_size).
My problem:
I want to implement a global binary operator+() that returns the result of the operation by value in the UInteger "generic" header file in this way:
template <typename Derived>
UInteger<Derived> operator+(const UInteger<Derived> &x0,
const UInteger<Derived> &x1)
{
Derived res = static_cast<Derived>(x0);
x0 += x1;
return x0;
}
The problem is that, since the result is returned by value, it is casted to the base class type loosing the implementation details (e.g. the m_data vector destructor is called).
Obviously I do not get this problem if I define the function to return a Derived type by value:
template <typename Derived>
Derived operator+(const UInteger<Derived> &x0,
const UInteger<Derived> &x1)
{
Derived res = static_cast<Derived>(x0);
x0 += x1;
return x0;
}
But I don't like too much this approach, epecially from a design point of view.
Is there a better solution to such problem? Maybe I should define such operators directly just for the derived classes?
Is there someone thinking that the CRTP is not very appropriate here and maybe is better to directly implement just one UInteger class in this way:
template <bool dynamic = true>
class UInteger
{
...
private:
std::array<uint32_t> m_data;
int m_len; <- how much of m_data array is actually in use
}
and if the bool "dynamic" value is false I never reallocate the vector obtaining something similar to the UIntegerFP template class. Maybe (if the compiler is smart enough) , since the boolean is a const template parameter, I also abtain something like conditional code compilation?!
Suggestions of any type are very welcome,
Thanks,
Davide
I don't quite understand why you want to use CRTP here in this way.
CRTP is the natural way to implement the actual details of the = and += operations, when only the memory management is done via the derived methods. Such design clearly separates the two task of arithmetic and memory management to different classes. The + (binary) operator is then best implemented as stand-alone function template.
Something like this:
namespace biginteger_details {
template<typename UInteger>
class UIntegerBase // CRTP base, implementing the arithmetics
{
using uint32_t = std::uint32_t;
using size_t = std::size_t;
// access to data: all functionality is implemented through these methods
uint32_t&block(size_t i) { return static_cast< UInteger*>(this)->m_data[i]; }
uint32_t block(size_t i) const { return static_cast<const UInteger*>(this)->m_data[i]; }
size_t size() const { return static_cast<const UInteger*>(this)->size(); }
void resize(size_t n) { static_cast<UInteger*>(this)->resize(n); }
public:
// assignment operator: allow assignment from any UInteger type
template<typename UI>
UInteger&operator=(UIntegerBase<UI> const&other)
{
resize(other.size());
for(size_t i=0; i!=size(); ++i)
block(i) = other.block(i);
return static_cast<UInteger&>(*this);
}
// add and assign: allow adding any UInteger type
template<typename UI>
UInteger&operator+=(UIntegerBase<UI> const&other)
{
// your code here using block(), size(), and resize()
return static_cast<UInteger&>(*this);
}
};
template<std::size_t nblock=8>
struct UIntegerFP
: UIntegerBase<UIntegerFP<nblock>>
{
static constexpr std::size_t max_blocks=nblock;
// 1 data
std::array<std::uint32_t,nblock> m_data;
std::size_t m_size=0;
// 2 interface to base
std::size_t size() const { return m_size; }
void resize(std::size_t n)
{
if(n>nblock) throw std::out_of_range("exceeding capacity");
m_size = n;
}
// 3 constructors
// copy constructor from any UInteger type
template<typename UI>
UIntegerFP(UIntegerBase<UI> const&other)
{ this->operator=(other); }
};
struct UIntegerAP
: UIntegerBase<UIntegerAP>
{
static constexpr std::size_t max_blocks=~(std::size_t(0));
// 1 data,
std::vector<std::uint32_t> m_data;
// 2 interface to base
std::size_t size() const { return m_data.size(); }
void resize(std::size_t n)
{ m_data.resize(n); }
// 3 constructors
// copy constructor from any UInteger type
template<typename UI>
UIntegerAP(UIntegerBase<UI> const&other)
{ this->operator=(other); }
};
// functions best take UIntegerBase<UI> arguments, for example:
// operator + as stand alone function template
template<typename Ulhs, typename Urhs>
inline std::conditional_t<(Ulhs::max_blocks > Urhs::max_blocks), Ulhs, Urhs>
operator+(UIntegerBase<Ulhs> const&lhs, UIntegerBase<Urhs> const&rhs)
{
std::conditional_t<(Ulhs::max_blocks > Urhs::max_blocks), Ulhs, Urhs>
result=lhs;
return result+=rhs;
}
} // namespace biginteger_details;
using biginteger_details::UIntegerFP;
using biginteger_details::UIntegerAP;
// note: biginteger_details::operator+ will be found by ADL (argument dependent look-up)
In your operator+ implementation, in practice, you are setting the function return type as:
UIntegerAP if one of the template types is an UIntegerAP.
Ulhs, otherwise (here I suppose you intend UIntegerFP).
Is that right?
Now... what if the UIntegerAP is a template as well? For example defined like this:
template <typename block_type>
class UIntegerAP
{
....
private:
std::vector<block_type> m_data;
}
I cannot use UIntegerAP type in the operator+ declaration anymore.
I am building a static library that will be used on many future projects. I do not want to limit the interface of a particular function in this static library so the application codes can have flexibility in data types. This library will hold pure virtual base class pointers to objects the user will need.
At first, I tried templating this function. However, I would have to template the pure virtual base function as well (In the code shown, BBInterface::Go) - apparently this is not possible.
The visitor pattern sounded like it might be applicable, but I'm afraid I just don't get it. I furthermore don't understand if I can keep the static library black-boxed, or if the static library would have to be re-compiled and linked with a new set of "visitors" anytime someone adds a possible data type.
I am now trying to create a templated struct inside the function, which is then reinterpret_cast-ed to a (hopefully?) equivalent struct. See below
This seems to work for two inputs (A and B). This is ok, but I'd ideally want to use variadic templates to have potentially many inputs. This is over my head at this point. If anyone could help with that, it'd be great.
So, is there a more elegant way to keep an extensible function interface (BBContainer::Do in the code below)? Is there a way to avoid reinterpret_cast? Can I extend this to more than two templated arguments? Is there a way to check for success of reinterpret_cast like dynamic_cast?
#include <iostream>
#include <vector>
#include <map>
#include <memory>
using namespace std;
static const double values[] = {0., 1., 2., 3., 4., 5., 6. };
// ------ ASSUME THIS BLACK BOX AREA IS IN A STATIC LIBRARY THE USER CAN NOT MODIFY -------//
struct BBPacket {};
class BBInterface
{
public:
virtual void Go(BBPacket&) = 0;
};
class BBContainer
{
public:
void Add(const string aName, std::unique_ptr<BBInterface>&& aThing)
{
BBMap[aName] = std::move(aThing);
}
template <typename A, typename B>
void Do(const std::string& aName, A& aVal, const B& aIndex)
{
struct NewPacket : public BBPacket
{
NewPacket(A& aVal, const B& aIndex) : mVal(aVal), mIndex(aIndex) {}
A& mVal;
const B& mIndex;
};
NewPacket temp(aVal, aIndex);
this->Do(aName, temp);
}
void Do(const string& aName, BBPacket& aPacket)
{
BBMap[aName]->Go(aPacket);
}
private:
map<std::string, unique_ptr<BBInterface>> BBMap;
};
// ----- The user area is written by the user, and should not be included in the blackbox project! ---------
struct USingleValuePacket
{
double& mVal;
const int& mIndex;
};
struct UVectorValuePacket
{
vector<double>& mVals;
const vector<int>& mIndices;
};
class USingleExtractor : public BBInterface
{
virtual void Go(BBPacket& aPacket)
{
USingleValuePacket& danger = reinterpret_cast<USingleValuePacket&>(aPacket);
fprintf(stdout, "The current single value is %1.1f\n", danger.mVal);
danger.mVal = values[danger.mIndex];
}
};
class UVectorExtractor : public BBInterface
{
virtual void Go(BBPacket& aPacket)
{
UVectorValuePacket& danger = reinterpret_cast<UVectorValuePacket&>(aPacket);
for (int i = 0; i < danger.mVals.size(); ++i)
{
fprintf(stdout, "The current vector value %i is %1.1f\n",i, danger.mVals[i]);
danger.mVals[i] = values[danger.mIndices[i]];
}
}
};
int main()
{
BBContainer a;
a.Add("f", std::unique_ptr<USingleExtractor>(new USingleExtractor));
a.Add("g", std::unique_ptr<UVectorExtractor>(new UVectorExtractor));
double val = 0.;
int index = 4;
a.Do("f", val, index);
fprintf(stdout, "Expected value is 4.0 and I get %1.1f\n", val);
std::vector<double> valVec(3);
std::vector<int> indexVec; indexVec.push_back(0); indexVec.push_back(2); indexVec.push_back(5);
a.Do("g", valVec, indexVec);
fprintf(stdout, "Expected value for index 0 is 0.0 and I get %1.1f\n", valVec[0]);
fprintf(stdout, "Expected value for index 1 is 2.0 and I get %1.1f\n", valVec[1]);
fprintf(stdout, "Expected value for index 2 is 5.0 and I get %1.1f\n", valVec[2]);
// a.Do("g", val, index); // This will go into UVectorExtractor with USingleValuePacket data - Not compatible!
return 0;
}
EDIT:
I want BBContainer::Do to have a flexible signature (in this example I use (string, double&, const int&) and (string, vector&, const vector&), but I may have many more). At the same time I do not want to modify BBInterface (for example with Go(double&, const int), Go(vector&, const vector&), and so on). I can assume the derived class of BBInterface knows what data its particular implementation of Go requires. So how do I forward generic data from BBContainer::Do to the derived classes of BBInterface when it only has access to the BBInterface base class - which is not allowed to be specialized? And, is there a more type-safe method than generating a templated struct in the BBInterface base class and using reinterpret_cast in its derived classes?
As Hurkyl pointed out, I should just make a packet. It seems good enough with that and a helper function to keep the interface of Do clean and a dynamic_cast instead of reinterpret_cast. I'm still working on variadic templates for variable length packets.
New packet:
template<typename A, typename B>
struct UPacket : public BBPacket
{
UPacket(A& aVal, const B& aIndex) : mVal(aVal), mIndex(aIndex) {}
A& mVal;
const B& mIndex;
};
Helper function:
template <typename A, typename B>
void Do(BBContainer& a, const string& aName, A& aVal, const B& aIndex)
{
a.Do(aName, UPacket<A, B>(aVal, aIndex));
}
Usage:
...
double val = 0.;
int index = 4;
Do(a,"f", val, index);
...
std::vector<double> valVec(3);
std::vector<int> indexVec; indexVec.push_back(0); indexVec.push_back(2); indexVec.push_back(5);
Do(a, "g", valVec, indexVec);