How does the C++ Standard defines the function generation of template classes - c++

I am currently investigating the code generation of templates and I don't know if what I see is compiler or C++ specific. And if it is compiler specific, all compilers do it that way.
If I pass a reference as class template parameter to the class, are only the functions shich use the parameter directly and the functions that call those functions generated in the executable?
struct TestConfiguration
{
uint8_t val1_;
};
template<const TestConfiguration& config>
class TestClass
{
public:
uint8_t writeData( uint8_t data )
{
return writeDataImpl( data, config);
}
uint8_t writeDataImpl( uint8_t data, const TestConfiguration& config_param)
{
data = data_ + config_param.val1_;
return data;
}
uint8_t getData()
{
return data_;
}
uint8_t data_;
};
constexpr TestConfiguration config2{0};
TestClass<config2> obj2;
constexpr TestConfiguration config1{0};
TestClass<config1> obj;
int main()
{
return obj2.writeData(2) + obj.writeData(3);
}
I would expect writeDataImpl and writeData to be generated both twice. However what the compiler does is generating writeData twice and writeDataImpl once. Is this common behaviour?

Related

Cast structs with certain common members

Lets say I have 2 structs:
typedef struct
{
uint8_t useThis;
uint8_t u8Byte2;
uint8_t u8Byte3;
uint8_t u8Byte4;
} tstr1
and
typedef struct
{
uint8_t u8Byte1;
uint8_t u8Byte2;
uint8_t useThis;
} tstr2
I will only need the useThis member inside a function, but in some cases I will need to cast one struct or the other:
void someFunction()
{
someStuff();
SOMETHING MyInstance;
if(someVariable)
{
MyInstance = reinterpret_cast<tstr1*>(INFO_FROM_HARDWARE); //This line of course doesn't work
}
else
{
MyInstance = reinterpret_cast<tstr2*>(INFO_FROM_HARDWARE); //This line of course doesn't work
}
MyInstance->useThis; //Calling this memeber with no problem
moreStuff();
}
So I want to use useThis no matter what cast was done. How can this be done?
I want to avoid someFunction() to be template (just to avoid this kind of things)
Note tha questions like this have a kind of similar problem but the struct members have the same order
EDIT:
In RealLife these structs are way larger and have several "same named" members. Directly casting a uint8_t as reinterpret_cast<tstr1*>(INFO_FROM_HARDWARE)->useThis would be tedious and require several reinterpret_casts (althought it's a working solution for my question before this EDIT). This is why I insist on MyInstance being "complete".
This is what templates are for:
template<class tstr>
std::uint8_t
do_something(std::uint8_t* INFO_FROM_HARDWARE)
{
tstr MyInstance;
std::memcpy(&MyInstance, INFO_FROM_HARDWARE, sizeof MyInstance);
MyInstance.useThis; //Calling this memeber with no problem
// access MyInstance within the template
}
// usage
if(someVariable)
{
do_something<tstr1>(INFO_FROM_HARDWARE);
}
else
{
do_something<tstr2>(INFO_FROM_HARDWARE);
}
I want to avoid someFunction() to be template (just to avoid this kind of things)
Why can’t I separate the definition of my templates class from its declaration and put it inside a .cpp file?
The linked problem isn't an issue for your use case because the potential set of template arguments is a finite set. The very next FAQ entry explains how: Use explicit instantiations of the template.
as suggested by AndyG, how about std::variant (there's no mention of the c++ standard you are using so maybe a c++17 solution is ok - also worth using <insert other variant implementation> if no c++17 available).
here's an example
std::variant knows what type is stored in it and you can use visit anytime you wish to use any of the members in there (snippet here for clarity):
// stolen from #eerrorika (sorry for that :( )
struct hardware {
uint8_t a = 'A';
uint8_t b = 'B';
uint8_t c = 'C';
uint8_t d = 'D';
};
struct tstr1 {
uint8_t useThis;
uint8_t u8Byte2;
uint8_t u8Byte3;
uint8_t u8Byte4;
};
struct tstr2 {
uint8_t u8Byte1;
uint8_t u8Byte2;
uint8_t useThis;
};
// stuff
if(true)
{
msg = *reinterpret_cast<tstr1*>(&hw);
}
else
{
msg = *reinterpret_cast<tstr2*>(&hw);
}
std::visit(overloaded {
[](tstr1 const& arg) { std::cout << arg.useThis << ' '; },
[](tstr2 const& arg) { std::cout << arg.useThis << ' '; }
}, msg);
EDIT: you can also do a variant of pointers
EDIT2: forgot to escape some stuff...
Using virtual dispatch is usually not what you want when mapping to hardware but it is an alternative.
Example:
// define a common interface
struct overlay_base {
virtual ~overlay_base() = default;
virtual uint8_t& useThis() = 0;
virtual uint8_t& useThat() = 0;
};
template<class T>
class wrapper : public overlay_base {
public:
template<class HW>
wrapper(HW* hw) : instance_ptr(reinterpret_cast<T*>(hw)) {}
uint8_t& useThis() { return instance_ptr->useThis; }
uint8_t& useThat() { return instance_ptr->useThat; }
private:
T* instance_ptr;
};
With that, you can declare a base class pointer, assign it, and use after the if statement:
int main(int argc, char**) {
std::unique_ptr<overlay_base> MyInstance;
if(argc % 2) {
MyInstance.reset( new wrapper<tstr1>(INFO_FROM_HARDWARE) );
} else {
MyInstance.reset( new wrapper<tstr2>(INFO_FROM_HARDWARE) );
}
std::cout << MyInstance->useThis() << '\n';
std::cout << MyInstance->useThat() << '\n';
}
Demo
Explanation regarding my comment: "It works, but unless the compiler is really clever and can optimize away the virtual dispatch in your inner loops, it's going to be slower than if you actually take the time to type cast":
Think of virtual dispatch as having a lookup table (vtable) used at runtime (which is often what actually happens). When calling a virtual function, the program has to use that lookup table to find the address of the actual member function to call. When it's impossible to optimize away the lookup (as I made sure in my example above by using a value only available at runtime) it does take a few CPU cycles extra compared to what you'd get by doing a static cast.
A simple reference to the struct member might be what you need:
uint8_t &useThis=SomeVariable
?reinterpret_cast<tstr1*>(INFO_FROM_HARDWARE)->useThis
:reinterpret_cast<tstr2*>(INFO_FROM_HARDWARE)->useThis;

Is it possible to enforce, at compile time, that two derived classes would always return different values for an overriding function?

Is it possible to enforce, at compile time, that the following is acceptable:
class B {
public:
virtual constexpr const char* getKeyStr() const = 0;
};
class D1 : public B {
public:
constexpr const char* getKeyStr() const override { return "D1"; }
};
class D2 : public B {
public:
constexpr const char* getKeyStr() const override { return "D2"; }
};
... But the following is not? We don't want D1 and D2 to return the same key string:
class B {
public:
virtual constexpr const char* getKeyStr() const = 0;
};
class D1 : public B {
public:
constexpr const char* getKeyStr() const override { return "D1"; }
};
class D2 : public B {
public:
constexpr const char* getKeyStr() const override { return "D1"; } // can we error out here at compile time?
};
Clarifications:
I am only showing two derived classes in this example, but what I am trying to achieve is to post this constraint on any number of derived classes.
The underlying problem to solve: I am thinking about a serializing/deserializing application, where each object with the same base class will be able to generate a textual/string representation of itself to be written to a file and, when given that string back (let's call it the content-string), will be able to reconstruct the corresponding data.
During deserialization, the application code should be able to tell from a key part of the content-string, let's call it the key-string, what derived object should be reconstructed. Therefore, the key-string needs to be unique for each derived class. I know type_info::name could be unique, but it is not customizable (or compiler independent?).
Regarding the getKeyStr() function (corresponding to the key-string mentioned above), it should always return the same string for all objects of the same derived class.
Yes, you can check at compile time if the "strings" returned by getKeyStr of D1, and D2 are different.
First provide a function that compares 2 const char * at compile time:
constexpr bool different(const char *x, const char *y)
{
while(*x != '\0' )
if (*x++ != *y++) return true;
return *y != '\0';
}
and then compare the returned values:
// this will trigger, if returned strings are the same.
static_assert( different(D1{}.getKeyStr(), D2{}.getKeyStr()) );
Edit: As #Jarod42 points out, string_view comparison is constexpr, and so the comparison function can be written much more simply:
constexpr bool different(const char * x, const char * y)
{
return std::string_view{x} != std::string_view{y};
}
Here's a demo.
If you can have a list of all Derived classes, you might do something like:
template <typename... Ts>
bool have_unique_keys()
{
std::string_view keys[] = {Ts{}.getKeyStr()...}; // requires constexpr default constructor
// static method might be more appropriate
std::sort(std::begin(keys), std::end(keys)); // constexpr in C++20
auto it = std::adjacent_find(std::begin(keys), std::end(keys));
return it != std::end(keys);
}
static_assert(have_unique_keys<>(D1, D2, D3));

Optimize destructors size away

I'm building a code for an embedded system and I'm trying to save as much binary space as necessary.
The code is for parsing a protocol (MQTT for what it's worth), where there are numerous packets type and they are all different, but share some common parts.
Currently, to simplify writing the code, I'm using this pattern :
template <PacketType type>
struct ControlPacket
{
FixedHeader<type> type;
VariableHeader<type> header;
Properties<type> props;
... and so on...
};
// Specialize for each type
template <>
struct FixedHeader<CONNECT>
{
uint8_t typeAndFlags;
PacketType getType() const { return static_cast<PacketType>(typeAndFlags >> 4); }
uint8 getFlags() const { return 0; }
bool parseType(const uint8_t * buffer, int len)
{
if (len < 1) return false;
typeAndFlags = buffer[0];
return true;
}
...
};
template <>
struct FixedHeader<PUBLISH>
{
uint8_t typeAndFlags;
PacketType getType() const { return static_cast<PacketType>(typeAndFlags >> 4); }
uint8 getFlags() const { return typeAndFlags & 0xF; }
bool parseType(const uint8_t * buffer, int len)
{
if (len < 1) return false;
typeAndFlags = buffer[0];
if (typeAndFlags & 0x1) return false; // Example of per packet specific check to perform
return true;
}
...
};
... For all packet types ...
This is working, and I'm now trying to reduce the binary impact of all those template specializations (else the code is almost duplicated 16 times)
So, I've came up to this paradigm:
// Store the most common implementation in a base class
struct FixedHeaderBase
{
uint8_t typeAndFlags;
virtual PacketType getType() { return static_cast<PacketType(typeAndFlags >> 4); }
virtual uint8 getFlags() { return 0; } // Most common code here
virtual bool parseType(const uint8_t * buffer, int len)
{
if (len < 1) return false;
typeAndFlags = buffer[0];
return true;
}
virtual ~FixedHeaderBase() {}
};
// So that most class ends up empty
template <>
struct FixedHeader<CONNECT> final : public FixedHeaderBase
{
};
// And specialize only the specific classes
template <>
struct FixedHeader<PUBLISH> final : public FixedHeaderBase
{
uint8 getFlags() const { return typeAndFlags & 0xF; }
bool parseType(const uint8_t * buffer, int len)
{
if (!FixedHeaderBase::parseType(buffer, len)) return false;
if (typeAndFlags & 0x1) return false; // Example of per packet specific check to perform
return true;
}
};
// Most of the code is shared here
struct ControlPacketBase
{
FixedHeaderBase & type;
...etc ...
virtual bool parsePacket(const uint8_t * packet, int packetLen)
{
if (!type.parseType(packet, packetLen)) return false;
...etc ...
}
ControlPacketBase(FixedHeaderBase & type, etc...) : type(type) {}
virtual ~ControlPacketBase() {}
};
// This is only there to tell which specific version to use for the generic code
template <PacketType type>
struct ControlPacket final : public ControlPacketBase
{
FixedHeader<type> type;
VariableHeader<type> header;
Properties<type> props;
... and so on...
ControlPacket() : ControlPacketBase(type, header, props, etc...) {}
};
This is working quite well and allows to shave a lot of the used binary code space. By the way, I'm using final here so the compiler could devirtualize, and I'm compiling without RTTI (obviously also with -Os and each function in its own section that are garbage collected).
However, when I inspect the symbol table size, I'm finding a lot of duplication on the destructors (all template instances are implementing a destructor which is clearly the same (same binary size) or empty).
Typically, I understand that ControlPacket<CONNECT> needs to call ~FixedHeader<CONNECT>() and that ControlPacket<PUBLISH> needs to call ~FixedHeader<PUBLISH>() upon destruction.
Yet, since all the destructor are virtual, is there a way that the specialization of ControlPacket avoid their destructors and instead have ControlPacketBase to destruct them virtually so that I don't ends up with 16 useless destructors but only one ?
It's worth pointing out that this is related to an optimization called "identical COMDAT folding", or ICF. This is a linker feature where identical functions (i.e., empty functions) are all merged into one.
Not every linker supports this, and not every linker is willing to do this (because the language says that different functions require different address), but your toolchain could have this. It would be fast and easy.
I'm going to assume your problem is reproduced with this toy example:
#include <iostream>
#include <memory>
#include <variant>
extern unsigned nondet();
struct Base {
virtual const char* what() const = 0;
virtual ~Base() = default;
};
struct A final : Base {
const char* what() const override {
return "a";
}
};
struct B final : Base {
const char* what() const override {
return "b";
}
};
std::unique_ptr<Base> parse(unsigned v) {
if (v == 0) {
return std::make_unique<A>();
} else if (v == 1) {
return std::make_unique<B>();
} else {
__builtin_unreachable();
}
}
const char* what(const Base& b) {
return b.what(); // virtual dispatch
}
const char* what(const std::unique_ptr<Base>& b) {
return what(*b);
}
int main() {
unsigned v = nondet();
auto packet = parse(v);
std::cout << what(packet) << std::endl;
}
The disassembly shows that both A::~A and B::~B have (multiple) listings, even though they are empty and identical. This is with = default and final.
If one removes virtual, then these vacuous definitions go away and we achieve the goal - but now when the unique_ptr deletes the object, we invoke undefined behavior.
We have three choices for leaving the destructor non-virtual while maintaining well-defined behavior, two of which are useful and one is not.
Non-useful: the first option is to use shared_ptr. This works because shared_ptr actually type-erases its deleter function (see this question), so at no point does it delete via the base. In other words, when you make a shared_ptr<T>(u) for some u deriving from T, the shared_ptr stores a function pointer to U::~U directly.
However, this type erasure simply reintroduces the problem and generates even more empty virtual destructors. See the modified toy example to compare. I'm mentioning this for completeness, in case you happen to already be putting these into shared_ptr's on the side.
Useful: the alternative is to avoid virtual dispatch for lifetime management, and use a variant. It's not really proper to make such a blanket statement, but generally you can achieve smaller code and even some speedup with tag dispatch, as you avoid specifying vtables and dynamic allocation.
This requires the largest change in your code, because the object representing your packet must be interacted with in a different way (it is no longer an is-a relationship):
#include <iostream>
#include <boost/variant.hpp>
extern unsigned nondet();
struct Base {
~Base() = default;
};
struct A final : Base {
const char* what() const {
return "a";
}
};
struct B final : Base {
const char* what() const {
return "b";
}
};
typedef boost::variant<A, B> packet_t;
packet_t parse(unsigned v) {
if (v == 0) {
return A();
} else if (v == 1) {
return B();
} else {
__builtin_unreachable();
}
}
const char* what(const packet_t& p) {
return boost::apply_visitor([](const auto& v){
return v.what();
}, p);
}
int main() {
unsigned v = nondet();
auto packet = parse(v);
std::cout << what(packet) << std::endl;
}
I used Boost.Variant because it produces the smallest code. Annoyingly, std::variant insists on producing some minor but present vtables for implementing itself - I feel like this defeats the purpose a bit, though even with the variant vtables the code remains much smaller overall.
I want to point out a nice result of modern optimizing compilers. Note the resulting implementation of what:
what(boost::variant<A, B> const&):
mov eax, DWORD PTR [rdi]
cdq
cmp eax, edx
mov edx, OFFSET FLAT:.LC1
mov eax, OFFSET FLAT:.LC0
cmove rax, rdx
ret
The compiler understands the closed set of options in the variant, the lambda duck-typing proved that each option really has a ...::what member function, and so it's really just picking out the string literal to return based on the variant value.
The trade-off with a variant is that you must have a closed set of options, and you no longer have a virtual interface enforcing certain functions exist. In return you get smaller code and the compiler can often see through the dispatch "wall".
However, if we define these simple visitor helper functions per "expected" member function, it acts as an interface checker - plus you've already got your helper class templates to keep things in line.
Finally, as an extension of the above: you are always free to maintain some virtual functions within the base class. This can offer the best of both worlds, if the cost of vtables is acceptable to you:
#include <iostream>
#include <boost/variant.hpp>
extern unsigned nondet();
struct Base {
virtual const char* what() const = 0;
~Base() = default;
};
struct A final : Base {
const char* what() const override {
return "a";
}
};
struct B final : Base {
const char* what() const override {
return "b";
}
};
typedef boost::variant<A, B> packet_t;
packet_t parse(unsigned v) {
if (v == 0) {
return A();
} else if (v == 1) {
return B();
} else {
__builtin_unreachable();
}
}
const Base& to_base(const packet_t& p) {
return *boost::apply_visitor([](const auto& v){
return static_cast<const Base*>(&v);
}, p);
}
const char* what(const Base& b) {
return b.what(); // virtual dispatch
}
const char* what(const packet_t& p) {
return what(to_base(p));
}
int main() {
unsigned v = nondet();
auto packet = parse(v);
std::cout << what(packet) << std::endl;
}
This produces fairly compact code.
What we have here is a virtual base class (but, no virtual destructor as it is not needed), and a to_base function that can take a variant and return for you the common base interface. (And in a hierarchy such as yours, you could have several of these per kind of base.)
From the common base you are free to perform virtual dispatch. This is sometimes easier to manage and faster depending on workload, and the additional freedom only costs some vtables. In this example, I've implemented what as first converting to the base class, and then perform virtual dispatch to the what member function.
Again, I want to point out the the definition of a visit, this time in to_base:
to_base(boost::variant<A, B> const&):
lea rax, [rdi+8]
ret
The compiler understands the closed set of classes all inherit from Base, and so doesn't have to actually examine any variant type tag at all.
In the above I used Boost.Variant. Not everyone can or wants to use Boost, but the principles of the answer still apply: store an object and track what type of object is stored in an integer. When it's time to do something, peek at the integer and jump to the right place in code.
Implementing a variant is a whole different question. :)

Combining typesafe code with runtime decisions

I am in the process of rewriting some existing code - where previously, all answer information was stored in a string array in memory. Based on the datatype, the data was transformed in various places. Below is a quick mock up of the setup I am aiming for. Essentially you have some questions - and the structure of the answers stored in the database depends on the datatype. Generally I avoid dealing with void*, and casting them to an appropriate type - but I couldn't find a better solution that would allow me to run generic code (by means of lambdas), or be specific if the datatype is known. Templated classes won't help in this case, as all the answers need to be stored in the same vector (as some arithmetic are applied to all answers based on predefined rules).
Any advice is appreciated.
#include <vector>
#include <memory>
struct AddressData
{
wchar_t Line1[50];
wchar_t Line2[50];
long CountrySeqNo;
AddressData()
{
memset(this, 0, sizeof(*this));
};
};
struct GenericData
{
wchar_t value[200];
GenericData()
{
memset(this, 0, sizeof(*this));
};
};
enum class DataType
: short
{
GENERIC,
ADDRESS
};
class AnswerBase
{
protected:
const void* const data;
const DataType dataType;
protected:
AnswerBase(const DataType datatype, const void* const _data)
: dataType(datatype), data(data)
{
if (data == nullptr)
throw std::exception("Data may not be initialized as NULL");
};
public:
/*
Some generic methods here that would apply logic by means of lambdas etc - these would be overwritten in the derived classes
*/
template<typename T> const T& GetData() { static_assert(false, "The given type is not supported"); };
template<>
const GenericData& GetData()
{
if (DataType::GENERIC != dataType)
throw std::exception("The requested type does not match the value that initialised data");
return *static_cast<const GenericData* const>(data);
};
template<>
const AddressData& GetData()
{
if (DataType::ADDRESS != dataType)
throw std::exception("The requested type does not match the value that initialised data");
return *static_cast<const AddressData* const>(data);
};
};
class AddressAnswer
: public AnswerBase
{
public:
AddressAnswer()
: AnswerBase(DataType::ADDRESS, &answer)
{
};
protected:
AddressData answer;
};
class GenericAnswer
: public AnswerBase
{
public:
GenericAnswer()
: AnswerBase(DataType::GENERIC, &answer)
{
};
protected:
GenericData answer;
};
int main()
{
std::vector<std::shared_ptr<AnswerBase>> answers;
answers.push_back(std::make_shared<GenericAnswer>());
answers.push_back(std::make_shared<AddressAnswer>());
// In some parts of code - interact with generic methods without needing to check the underlying data type
// ....
// ....
// In parts of code where we know we are dealing with a given type - like saving to a DB
auto val1 = answers[0]->GetData<GenericData>().value;
auto val2 = answers[1]->GetData<AddressData>().Line1;
// this will give a runtime failure
//auto val3 = answers[0]->GetData<AddressData>().Line1;
return 0;
}
variant is the clean way to do this. Store it in the parent.
Alternatively, provide a variant<A,B> GetData() in the parent. Now visiting is encapsulated in the variant returned. The parent stores the data.
Alternatively, provide a virtual variant<A,B> GetData() = 0. The child type returns the data, either A or B, in the variant in question.
Alternatively, write virtual A* GetA() = 0; virtual B* GetB() = 0;. Then maybe write a template method called GetData<T> such that GetData<A>() calls GetA, etc.
Alternatively, write virtual A* Get(tag_t<A>) = 0; virtual B* Get(tag_t<B>)=0;, where
template<class T>
struct tag_t {
using type=T;
constexpr tag_t(){}
};
template<class T>
constexpr tag_t<T> tag{};
is a tag used for dispatching. Now you can call the right virtual interface by doing a Get(tag<AddressData>).
In these virtual cases, the data is stored in the derived type.

C++ BigIntegers and the Curiously Recurring Template Pattern Issue

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.