Francis Cugler
Francis Cugler

Reputation: 7905

Storing a pointer to a template type of the same template class

Let's say we have the following:

template<typename A, typename B>
class Foo {
private:
    A m_a;
    B m_b;
    Foo<A,B>* m_pFoo;

public:
    Foo( A a, B b, Foo<A,B>* pFoo = nullptr );
};

With this class I can save any pFoo into m_pFoo as long as the instantiation of types match as such:

 int main() {
     Foo<int, int> foo1( 3, 5 );
     Foo<int, int> foo2( 2, 4, &foo1 ); // This Works
     Foo<int, int> foo3( 5, 7, &foo2 ); // This Still Works 

     Foo<double, int> foo4( 2.4, 5 );
     Foo<double, int> foo5( 7.5, 2, &foo4 ); // This Works
     Foo<double, int> foo6( 9.2, 6, &foo5 ); // This Still Works

     // Compiler Error - Can not deduce template arguments
     Foo<double, int> foo7( 3.7, 2, &foo1 ); // Doesn't Work

     return 0;
 }

In a previous question I demonstrated a similar problem as this and my initial question was how to pass a pointer to a class template type into the same class template constructor, however one of the responses I received regarding this was that passing in the pointer is not the problem, but the storage is. So with this post my new question becomes this:

How would I be able to have the same class template with the same or similar constructor as above where each of these types:

Foo<short, short>           ssFoo;
Foo<short, int>             siFoo;
Foo<short, int64_t>         si64Foo;
Foo<short, unsigned>        suFoo;
Foo<short, float>           sfFoo;
Foo<short, double>          sdFoo;
Foo<short, long>            slFoo;
Foo<short, long long>       sllFoo;

Foo<int, short>             isFoo;
Foo<int, int>               iiFoo;
Foo<int, int64_t>           ii64Foo;
Foo<int, unsigned>          iuFoo;
Foo<int, float>             ifFoo;
Foo<int, double>            idFoo;
Foo<int, long>              ilFoo;
Foo<int, long long>         illFoo;

Foo<int64_t, short>        i64sFoo;
Foo<int64_t, int>          i64iFoo;
Foo<int64_t, int64_t>      i64i64Foo;
Foo<int64_t, unsigned>     i64uFoo;
Foo<int64_t, float>        i64fFoo;
Foo<int64_t, double>       i64dFoo;
Foo<int64_t, long>         i64lFoo;
Foo<int64_t, long long>    i64llFoo;

Foo<unsigned, short>        usFoo;
Foo<unsigned, int>          uiFoo;
Foo<unsigned, int64_t>      ui64Foo;
Foo<unsigned, unsigned>     uuFoo;
Foo<unsigned, float>        ufFoo;
Foo<unsigned, double>       udFoo;
Foo<unsigned, long>         ulFoo;
Foo<unsigned, long long>    ullFoo;

Foo<float, short>           fsFoo;
Foo<float, int>             fiFoo;
Foo<float, int64_t>         fi64Foo;
Foo<float, unsigned>        fuFoo;
Foo<float, float>           ffFoo;
Foo<float, double>          fdFoo;
Foo<float, long>            flFoo;
Foo<float, long long>       fllFoo;

Foo<double, short>          dsFoo;
Foo<double, int>            diFoo;
Foo<double, int64_t>        di64Foo;
Foo<double, unsigned>       duFoo;
Foo<double, float>          dfFoo;
Foo<double, double>         ddFoo;
Foo<double, long>           dlFoo;
Foo<double, long long>      dllFoo;

Foo<long, short>            lsFoo;
Foo<long, int>              liFoo;
Foo<long, int64_t>          li64Foo;
Foo<long, unsigned>         luFoo;
Foo<long, float>            lfFoo;
Foo<long, double>           ldFoo;
Foo<long, long>             llFoo;
Foo<long, long long>        l_llFoo;

Foo<long long, short>       llsFoo;
Foo<long long, int>         lliFoo;
Foo<long long, int64_t>     lli64Foo;
Foo<long long, unsigned>    lluFoo;
Foo<long long, float>       llfFoo;
Foo<long long, double>      lldFoo;
Foo<long long, long>        ll_lFoo;
Foo<long long, long long>   ll_llFoo;

Are all valid types to store within the class template upon construction where the address of the previous instance is being passed into the new instance's constructor? Also; how would I be able to prevent this class from accepting anything that is a custom or user defined object or character, string types, enumerations and Boolean types? I would like the typenames being passed into the class templates argument list as being numerical types only.

Upvotes: 0

Views: 1161

Answers (2)

Brandon Haston
Brandon Haston

Reputation: 434

Let's address the compiler error and why it's happening first: The reason the template arguments can't be deduced is because the constructor accepts a pointer to type Foo<A, B>. When you define foo7, you have made a foo7 of type Foo<double, int>. You then attempt to pass a reference to foo1 to its parameter which is of type Foo<int, int> but the constructor expects a Foo<double, int>.

To deal with this, one could create a third template type to be able to take in any other kind of Foo while not being restricted to the current type being constructed. I apologize if this isn't worded very well. I wrote an example:

template<typename A, typename... Rest>
class Foo;

template<typename A, typename B, typename C>
class Foo<A, B, C>{
private:
    A m_a;
    B m_b;
    C* m_pFoo;

public:
    Foo(A a, B b, C* p_Foo);
};

template<typename A, typename B>
class Foo<A, B>{
private:
    A m_a;
    B m_b;
    Foo<A, B>* m_pFoo;

public:
    Foo(A a, B b, Foo<A, B>* p_Foo = nullptr);
};

Here, we've declared a class, Foo, that can take 1 or more template arguments. Following this, there's a definition for a Foo that takes three template arguments, and a Foo that takes only two template arguments. With the three-argument definition, we have a third type which will be the type of the m_pFoo pointer. In the two-argument definition we declare m_pFoo to be of type Foo<A, B>* (this is the initial behavior of your original code)

int main() {
    Foo<int, int> foo1(3, 5);
    Foo<int, int, decltype(foo1)> foo2(2, 4, &foo1); 
    Foo<int, int, decltype(foo2)> foo3(5, 7, &foo2); 

    Foo<double, int> foo4(2.4, 5);
    Foo<double, int, decltype(foo4)> foo5(7.5, 2, &foo4);
    Foo<double, int, decltype(foo5)> foo6(9.2, 6, &foo5); 

    //Now works.
    Foo<double, int, decltype(foo1)> foo7(3.7, 2, &foo1); 

    return 0;
} 

Now we can pass in the type of the foo instance as a template argument using decltype (added in C++11).

It's not 'automatic' and still requires you to explicitly state the type of the foo you want to pass to the constructor, but I think this should be pretty close to what you're trying to do.

Upvotes: 1

Sam Varshavchik
Sam Varshavchik

Reputation: 118340

An instance of a template is a completely distinct type, separate from all other types, and other instances of the template.

Foo<short, short>

and

Foo<int, int>

are two different classes, which are just as different from each other as

Foo1;

and

Foo2;

are different from each other.

These are different classes. It follows that:

Foo<short, short> *m_pFoo;

and

Foo<int, int> *m_pFoo;

are also as different from each other as

Foo1 *m_pFoo;

and

Foo2 *m_pFoo;

are. C++ just doesn't work this way. The m_pFoo member of your template can only point to one class. You have to pick which one it is. It could be the same type as its own class, that's one option. Or, it can point to an instance of some other class. But it can only be one class.

Unless, of course, you make it a

void *m_pFoo;

But, you'll lose type safety and type checking, of course, going that route.

As it's been mentioned in the comments, you might be able to derive the template from a superclass, and store a pointer to the superclass:

class FooBase {

// ...

};

template<typename A, typename B> class Foo : public FooBase {

    A m_a;
    B m_b;
    FooBase* m_pFoo;

public:
    Foo( A a, B b, FooBase* pFoo = nullptr );
};

So, you'll be able to pass a pointer to any Foo<A, B> to the constructor, which will be automatically casted to the superclass, and only the pointer to the superclass will get stored.

This approach, of course, has many other implications, but this would be the cleanest, most type-safe approach -- at least up to this point -- that I can think of.

Upvotes: 3

Related Questions