How to Define Different Types for the Same Class in C++

How to define different types for the same class in C++

A common technique is to have a class template where the template argument simply serves as a unique token (“tag”) to make it a unique type:

template <typename Tag>
class Fruit {
int p;
public:
Fruit(int p) : p(p) { }
int price() const { return p; }
};

using Apple = Fruit<struct AppleTag>;
using Banana = Fruit<struct BananaTag>;

Note that the tag classes don’t even need to be defined, it’s enough to declare a unique type name. This works because the tag isn’s actually used anywhere in the template. And you can declare the type name inside the template argument list (hat tip to @Xeo).

The using syntax is C++11. If you’re stuck with C++03, write this instead:

typedef Fruit<struct AppleTag> Apple;

If the common functionality takes up a lot of code this unfortunately introduces quite a lot of duplicate code in the final executable. This can be prevented by having a common base class implementing the functionality, and then having a specialisation (that you actually instantiate) that derives from it.

Unfortunately, that requires you to re-implement all non-inheritable members (constructors, assignment …) which adds a small overhead itself – so this only makes sense for large classes. Here it is applied to the above example:

// Actual `Fruit` class remains unchanged, except for template declaration
template <typename Tag, typename = Tag>
class Fruit { /* unchanged */ };

template <typename T>
class Fruit<T, T> : public Fruit<T, void> {
public:
// Should work but doesn’t on my compiler:
//using Fruit<T, void>::Fruit;
Fruit(int p) : Fruit<T, void>(p) { }
};

using Apple = Fruit<struct AppleTag>;
using Banana = Fruit<struct BananaTag>;

C++same class as member in class

The reason is that the type of the member m_Mother has incomplete type at the point it is declared.

If you think about it. If it would have worked, you would create an object with an object inside with the same type, which in turn always have an object of the same type inside (and so on). The object would in a sense have infinite size.

One solution is to keep a pointer to the parent class instead.

class clsNode
{
private:
clsNode* m_Mother;
public:
void setMother(clsNode* uNode){ m_Mother=uNode; }
};

If you would like to have all parents always be alive during the lifetime of their children, you could use a shared pointer instead of a raw pointer.

class clsNode
{
private:
std::shared_ptr<clsNode> m_Mother;
public:
void setMother(std::shared_ptr<clsNode> uNode){ m_Mother=uNode; }
};

If you go with this solution you would originally create your objects with make_shared

How to store object of different class types into one container in modern c++?

You can store different object types in a std::variant. If you do so, there is no need to have a common interface and use virtual functions.

Example:

class A
{
public:
void DoSomething() { std::cout << "DoSomething from A" << std::endl; }
};

class B
{
public:
void DoSomething() { std::cout << "DoSomething from B" << std::endl; }
};

int main()
{
std::vector< std::variant< A, B > > objects;

objects.push_back( A{} );
objects.push_back( B{} );

for ( auto& obj: objects )
{
std::visit( [](auto& object ){ object.DoSomething(); }, obj);
}
}

But using this solutions can have also drawbacks. Access via std::visit may be slow. Sometimes e.g. gcc generates very bad code in such situations. ( jump table is generated in runtime, no idea why! ). You always call the function via table access which takes some additional time. And storing the objects in std::variant consumes always the size of the biggest class you have in the variant and in addition you need some space for the tag variable inside the variant.

The "old" way is to store raw or better smart-pointers into the vector and simply call via base pointer the common interface functions. The drawback here is the additional vtable pointer in each instance ( which is typically the same size as the tag variable in the std::variant ). The indirection with vtable access to call the function comes also with a ( small ) cost.

Example with smart pointer of base type and vector:

class Interface
{
public:
virtual void DoSomething() = 0;
virtual ~Interface() = default;
};

class A: public Interface
{
public:
void DoSomething() override { std::cout << "DoSomething from A" << std::endl; }
virtual ~A(){ std::cout << "Destructor called for A" << std::endl; }
};

class B: public Interface
{
public:
void DoSomething() override { std::cout << "DoSomething from B" << std::endl; }
virtual ~B(){ std::cout << "Destructor called for B" << std::endl; }
};

int main()
{
std::vector< std::shared_ptr<Interface>> pointers;

pointers.emplace_back( std::make_shared<A>() );
pointers.emplace_back( std::make_shared<B>() );

for ( auto& ptr: pointers )
{
ptr->DoSomething();
}
}

If std::unique_ptr is sufficient for you, you can use that one. It depends on the need of passing pointers around or not in your design.

Hint: If you are using pointers to base class type never forget to make your destructors virtual! See also: When to use virtual destructors

In your case I would vote to use smart-pointers of base class type in simple vector!

BTW:

virtual auto ObjType(void) -> TYPES

That look ugly to me! No need for auto here as the return type is known before you write the function parameter list. In such cases, where template parameters are need to be deduced to define the return type, it is needed, but not here! Please do not use always auto!

Same class name in different C++ files

This is a violation of the one definition rule (C++03, 3.2/5 "One definition rule"), which says (among other things):

There can be more than one definition of a class type (clause 9), ...
in a program provided that each definition appears in a different
translation unit, and provided the definitions satisfy the following
requirements. Given such an entity named D defined in more than one
translation unit, then

  • each definition of D shall consist of the same sequence of tokens;

If you violate the one definition rule, the behavior is undefined (which means that strange things can happen).

The linker sees multiple definitions of Student::foo() - one in a's object file and one in b's. However it doesn't complain about this; it just selects one of the two (as it happens, the first one it comes across). This 'soft' handling of duplicate functions apparently happens only for inline functions. For non-inline functions, the linker will complain about multiple definitions and will refuse to produce an executable (there may be options that relax this restriction). Both GNU ld and MSVC's linker behave this way.

The behavior makes some sense; inline functions need to be available in every translation unit they're used in. And in the general case they need to have non-inline versions available (in case the call isn't inlined or if the function's address is taken). inline is really just a free pass around the one-definition rule - but for it to work, all the inline definitions need to be the same.

When I look at dumps of the object files, I don't see anything obvious that explains to me how the linker knows that one function is permitted to have multiple definitions and others aren't, but I'm sure there's some flag or record which does just that. Unfortunately, I find that the workings of the linker and object file details aren't particularly well documented, so the precise mechanism will probably remain a mystery to me.

As for your second question:

As an extra question, if classes are defined in header files, should
they always be wrapped with unnamed namespace to avoid such situation?
Is there any side effects?

You almost certainly don't want to do this each class would be a distinct type in each translation unit, so technically instances of the class they couldn't be passed from one translation unit to another (by pointer, reference or copying). Also, you'd end up with multiple instances of any static members. That probably wouldn't work well.

Put them in different, named namespaces.

How can I declare a member vector of the same class?

This paper was adopted into C++17 which allows incomplete types to be used in certain STL containers. Prior to that, it was Undefined Behavior. To quote from the paper:

Based on the discussion on the Issaquah meeting, we achieved the
consensus to proceed* with the approach – “Containers of Incomplete
Types”, but limit the scope to std::vector, std::list, and
std::forward_list, as the first step.

And as for the changes in the standard (emphasis mine):

An incomplete type T may be used when instantiating vector if the
allocator satisfies the allocator-completeness-requirements
(17.6.3.5.1). T shall be complete before any member of the resulting
specialization of vector is referenced.

So, there you have it, if you leave the default std::allocator<T> in place when instantiating the std::vector<T, Allocator>, then it will always work with an incomplete type T according to the paper; otherwise, it depends on your Allocator being instantiable with an incomplete type T.


A is an incomplete type, right? If there was a vector of A*s I would understand. But here I don't understand how it works. It seems to be a recursive definition.

There is no recursion there. In an extremely simplified form, it's similar to:

class A{
A* subAs;
};

Technically, apart from size, capacity and possibly allocator, std::vector only needs to hold a pointer to a dynamic array of A it manages via its allocator. (And the size of a pointer is known at compile time.)

So, an implementation may look like this:

namespace std{

template<typename T, typename Allocator = std::allocator<T>>
class vector{

....

std::size_t m_capacity;
std::size_t m_size;
Allocator m_allocator;
T* m_data;
};

}

c++ class declaration when same routine, different member variable type

You can use a templated class:

template <typename NetworkType>
class NetworkClass
{
NetworkType tcp_socket_{io_service_};
};

and then use:

using IPclass = NetworkClass<tcp::socket>;

If you need more than this, you can use type traits to define more advanced behaviors without dynamic polymorphism.

same class, different size...?

You violated the requirements of One Definition Rule (ODR). The behavior of your program is undefined. That's the only thing that's going on here.

According to ODR, classes with external linkage have to be defined identically in all translation units.

How to write the same function with same name that handles different class arguments almost similarly in C++?

Use a template function:

#include <iostream>
#include <type_traits>

struct A {
char const* data;
};

struct B {
char const* data;
};

template <typename T,
std::enable_if_t<std::is_same_v<T, A> || std::is_same_v<T, B>, int> = 0
>
void doSomething(T const& arg) {
std::cout << arg.data << '\n';
}

int main() {
A a{"Hello "};
B b{"World"};
foo(a);
foo(b);
// foo("something else"); // Doesn't compile
}

Slightly less cluttered with C++20 concepts:

#include <concepts>

template <typename T>
void doSomething(T const& arg) requires (std::same_as<T, A> || std::same_as<T, B>) {
std::cout << arg.data << '\n';
}

You could even over-engineer such a concept into your code-base if this is a common issue you have:

template <typename T, typename ...Types>
concept one_of = (std::same_as<T, Types> || ...);

template <one_of<A, B> T>
void doSomething(T const& arg) {
std::cout << arg.data << '\n';
}


Related Topics



Leave a reply



Submit