Sign in to follow this  
yacwroy

pointer to subclass as template parameter

Recommended Posts

I have a template function (zug) that takes a pointer to a CFoo as its parameter. I have a global variable (bar) whose type (CBar) is derived from CFoo. Is it possible to invoke zug() with bar's address as the template parameter? I can't see any reason why not, but every method I try seems to fail.
struct CFoo {};
struct CBar : public CFoo {};

template
  <CFoo* P>
void zug() {}

CFoo foo;
CBar bar;
CFoo& bar_as_foo(bar);

CFoo* const bar_foo_ptr = &bar;
const CFoo* const c_bar_foo_ptr = &bar;

int main()
  {
    zug<&foo>(); // Works fine.
    zug<&bar>(); // 1. G++: error: no matching function for call to 'zug()'
    zug<(CFoo*)(&bar)>(); // 2. G++: error: 'bar' cannot appear in a constant-expression.  etc..
    zug<&bar_as_foo>(); // 3. G++: error: no matching function for call to 'zug()'
    zug<bar_foo_ptr>(); // 4. G++: error: 'bar_foo_ptr' cannot appear in a constant-expression
    zug<c_bar_foo_ptr>(); // 5. G++: error: 'c_bar_foo_ptr' cannot appear in a constant-expression.
    
    zug<(CFoo*)(&bar_as_foo)>(); // 6. G++: error: 'bar_as_foo' cannot appear in a constant-expression.  etc...
    zug<bar>(); // 7. G++: error: 'bar' is not a valid template argument because 'bar' is a variable, not the address of a variable
    
    zug<&((CFoo&)bar)>(); // 8. G++: error: parse error in template argument list.
    ((CFoo&)bar) = foo; // Syntax works here though.
  }
Does anyone know how to do this, or if it's impossible? Thanks. (g++ 4.5.2 / ubuntu 64 bit)

Share this post


Link to post
Share on other sites
Template parameters are compile time, pointers to instances are run time. The reason the first works it that the type is correct, but the address is not passed to the function and can not be used as is. You would need to change it to take an instance as a function parameter.

Share this post


Link to post
Share on other sites
Quote:
Original post by CmpDev
Template parameters are compile time, pointers to instances are run time. The reason the first works it that the type is correct, but the address is not passed to the function and can not be used as is.


The following shows zug() using the instance. The pointer to foo is constant, and thus available compile-time.

#include <iostream>

struct CFoo
{
void say()
{ std::cout << this << std::endl; }
};

struct CBar : public CFoo {};

template
<CFoo* P>
void zug()
{
P->say();
}

CFoo foo;

int main()
{
zug<&foo>();
std::cout << "foo @ " << &foo << std::endl;
}


Compiles fine and outputs:
0x601180
foo @ 0x601180


In the original example, bar's address is also available at compile time. bar is a CBar, and CBar is derived from CFoo, so bar is a CFoo.

&bar is a CFoo* and is available at compile-time (just like &foo). Yet &foo works and &bar doesn't.

Share this post


Link to post
Share on other sites
Quote:
What are you really trying to do? And why?


I'm resurrecting an old project of an allocator library. I'm just working on making the interface tidier.
The library guarantees a minimum space efficiency (committed memory is kept under 2*(requested memory) + ~10MB), yet allocation operations are O(ln N) so there aren't any freezes due to compacting memory.
To do this the allocator must be given autonomy and allowed to move allocated chunks at will, so the chunks contain a pointer to a move function that keeps links etc intact after moving.
Due to this extra type-specific information requirement, I can't just overload new and delete.

I have an allocator object class that provides the alloc() and dealloc() calls.

Like STL I want to allow the use of multiple allocators (here, two can operate safely in different threads without blocking eachother).

STL uses an allocator trait class as a (defaulted) template parameter in classes such as std::vector. But I think it'd be nicer to use vector<&allocator_pointer> than vector<trait_class> as you avoid having to write trait_class.

All works well for a single type of allocator class. However, I now want to make my allocator class abstract, mainly because there can be different patterns of allocation that can be modeled in different ways.

eg:
class CDerivedAllocator : public CAllocator
{
// ...
};

CDerivedAllocator my_allocator;

// Now for a class that uses my allocator:
template <CAllocator* alloc>
class CObject
{
// ...
};

CObject<&my_allocator> object; // <- this is the problem.



I hope that makes sense.
It's not an insurmountable problem, but it would be an improvement, and I can't see why it's not possible.

Share this post


Link to post
Share on other sites
I can point out where in the standard where it seems to disallow what you're trying to do (Section 14.3.2 paragraph 5, second bullet point), but I don't know why the rule exists.

Share this post


Link to post
Share on other sites
Quote:
Original post by Bregma
Quote:
Original post by SiCrane
I don't know why the rule exists.

C++ does not support contravariance.


Explain?

Share this post


Link to post
Share on other sites
Quote:
Does anyone know how to do this, or if it's impossible?

Templates operate on types. A Java equivalent would be Class instance.

In addition, integral integer types may be used as parameters, and those that map directly to them (bool, enum).

I'm not sure of exact reasons, but the above are platform-independent. Pointers, doubles, references are not. Addresses might not even be unique - under DOS memory model different addresses might map to same physical memory.

Address is not known until link time.
int bar;

int main(int argc, char* argv[])
{
foo<(size_t)&bar>();
}
The reason compiler barfs on this is that location, or even existence of bar is determined by the linker - compiler only emits a symbol, and template expansion is completed before then.

Templates should be thought of as macros, they are interpreted in a similar way. They have no knowledge of semantics, they work on opaque symbols. When done, the resulting code is passed to compiler which performs the optimizations and error checking. Historically, this caused code bloat, but compilers these days are smart enough to avoid it.

So the above is same as if asking pre-processor to do something like:
#define a &bar
// if &bar is not null
#IF a
It will be true, since a is defined as '&bar', but not because &bar is non-null. Pre-processor doesn't understand what &bar is, it treats it just as a bunch of characters.

This is also part of the reason why error messages from templates are such a complete mess.

Share this post


Link to post
Share on other sites
Nevermind....
try this approach.


#include <stdio.h>
class CAllocator
{
public:
virtual ~CAllocator()
{
}
virtual void alloc() const
{
printf("standard alloc\n");
}
};

template<class A = CAllocator>
class CAllocUser
{
public:
CAllocUser(void) : Allocator(new A)
{

}
virtual ~CAllocUser()
{

}
const CAllocator* getAllocator(void) const
{
return Allocator;
}
protected:
const CAllocator* Allocator;
};

class MyAllocator : public CAllocator
{
public:
void alloc() const
{
printf("coustum alloc\n");
}
};

template<class A>
class CObjectA : public CAllocUser<A>
{
public:
void run()
{
this->getAllocator()->alloc();
}
};

class CObjectB : public CAllocUser<>
{
public:
void run()
{
this->getAllocator()->alloc();
}
};

int main()
{
CObjectA<MyAllocator> objA;
CObjectB objB;

objA.run();
objB.run();
return 0;
}

Share this post


Link to post
Share on other sites
Quote:
Original post by Sudi
Nevermind....
try this approach.


This doesn't solve the original problem of state. The problem with std::allocator design is that it cannot pass its internal state on per-instance basis, so either the owning instance needs to store the allocator instance which adds memory overhead, or the allocator's state must be static.

For this purpose, templates are inadequate due to the way they define equality and equivalence. Similar problem as
char * a, b;
a == b is not necessarily the same as strcmp(a,b)


There is however a way around it:
template < class Storage >
class storage_ptr {
static Storage;
};

template < class T > struct Allocator {}

struct IntBuffer {
int data[2000];
}
typedef Allocator<storage_ptr<IntBuffer> > IntAllocator;


This would also allow traits to convert between storage types and allow cross-allocator assignments, but to define a different pointer, a new type must be defined. Also, typedefed instances would almost certainly point to same memory, and it's not entirely impossible for distinct types to be compiled into same code:
struct A { int data[2000]; };
struct B { int data[2000]; };
storage_ptr<A> might be same instance as storage_ptr< B >

Share this post


Link to post
Share on other sites
I have another solution: just add an extra layer of indirection (ie: pointer to pointer).
#include <iostream>

class CFoo {};
class CBar : public CFoo {};

CBar bar;
extern CFoo* const bar_ptr;
CFoo* const bar_ptr = &bar;

template
<CFoo* const* rPTR>
void zug()
{
std::cout << (*rPTR) << std::endl;
std::cout << &bar << std::endl;
}

int main()
{
zug<&bar_ptr>();
}


Output:
0x601180
0x601180



Quote:
Original post by AntheusThe reason compiler barfs on this is that location, or even existence of bar is determined by the linker - compiler only emits a symbol, and template expansion is completed before then.


I'm pretty sure that template expansion is allowed to complete even if the pointer is only an unresolved symbol. At least, that's what seems to be happening. Anywhere the pointer is used in the template the object code will still have the symbol in it.



Quote:
Original post by Sudi
Nevermind....
try this approach.

That's the backup plan if I can't get this fixed and decide not to use the above method.




BTW, here's the offending bit of standard (Thanks to SiCrane). Bold-ing is mine.:
Quote:
Original post by C++ International Standard 2008. 14.3.2 para 5 item 2for a non-type template-parameter of type pointer to object, qualification conversions (4.4) and the
array-to-pointer conversion (4.2) are applied; if the template-argument is of type std::nullptr_t, the
null pointer conversion (4.10) is applied. [ Note: In particular, neither the null pointer conversion for
a zero-valued integral constant expression (4.10) nor the derived-to-base conversion (4.10) are applied.
Although 0 is a valid template-argument for a non-type template-parameter of integral type, it is not
a valid template-argument for a non-type template-parameter of pointer type. However, both (int*)0
and nullptr are valid template-arguments for a non-type template-parameter of type “pointer to int.”
— end note ]

So, if I read that right, it seems it doesn't work simply because someone specifically excluded it. Perhaps there could be some issue with using it that I/we haven't thought of.

Share this post


Link to post
Share on other sites
Quote:
Original post by yacwroy
I have another solution: just add an extra layer of indirection (ie: pointer to pointer).
*** Source Snippet Removed ***


The reason it works is due to extern. Extern pointers can be passed as template arguments.

From design perspective, the solution with templated storage parameter is identical.

Given the pointers are hard-coded during compile-time, the following is also identical:
template <class Storage>
void zug() {
Storage::GetInstance(); // a singleton
}
This also avoids static initialization order fiasco, it is just clearer regarding the intent and terminology.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this