1.11M Members

Templates and context passing

 
0
 

I have a some libraries which have a lot of common classes but the namespaces are different. Most of the code I was writing could be easily reused by classes in both libraries so after some reading I tried a technique of using the templates to pass namespace. As you can see in the program below. However the issue is that not all classes are common so I create different templates to pass to Util. (templateNamespace and templateNamespaceb)

I was expecting that if I don't pass a type and not call a function it should not get generated and things should be fine. However it only works if the the type is not in the function signature. So for eg in the code below, namespace B does not have the type 'T2' and 'print2' in Util depends on T2. If I change print2 to 'void print2(typename schemans::T2 t2)' this program does not compile saying 'B' does not have T2 but if I declare it inside the function it compiles fine. could someone please explain the behaviour ? I hope I have explained it well if not let me know and i'll try again

#include <iostream>


namespace A
{
    class T1{
        public:
            int i;
    };

    class T2{
        public:
            int x;
    };
};

namespace B
{
    class T1{
        public:
            int i;
    };
};

template <typename schemans>
class util{
    public:
        void print(typename schemans::T1 tobj)
        {
            std::cout << tobj.i << std::endl;
        }

        void print2()
        {
            typename schemans::T2 t2;
            t2.x = 13;
            std::cout << t2.x << std::endl;
        }
};

template <typename test, typename test2>
struct templateNamespace{
    typedef test    T1;
    typedef test2    T2;
};

template <typename test>
struct templateNamespaceb{
    typedef test    T1;
};

int main(int agrc, char* argv[])
{
    typedef templateNamespace<A::T1, A::T2> a_NS;
    util<a_NS> utilObj;

    A::T1 t1;
    t1.i = 10;
    A::T2 t2;
    t2.x = 12;
    utilObj.print(t1);
    utilObj.print2();

    typedef templateNamespaceb<B::T1> b_NS;
    util<b_NS> utilObj2;
    B::T1 t3;
    t3.i = 11;
    utilObj2.print(t3); 
}
 
1
 

I have a some libraries which have a lot of common classes but the namespaces are different. Most of the code I was writing could be easily reused by classes in both libraries so after some reading I tried a technique of using the templates to pass namespace.

That is a very ugly and awkward technique. I can think of a thousand better ways of achieving the same effect. One typical way is using a tag dispatching mechanism with meta-functions, as so:

struct dummy_type { };

template <typename Tag>
struct get_T1 { typedef dummy_type type; };

template <typename Tag>
struct get_T2 { typedef dummy_type type; };


namespace A {

    class T1 {
        public:
            int i;
    };

    class T2 {
        public:
            int x;
    };

};

struct A_tag { };

template <> struct get_T1<A_tag> { typedef A::T1 type; };
template <> struct get_T2<A_tag> { typedef A::T2 type; };


namespace B {

    class T1 {
        public:
            int i;
    };

};

struct B_tag { };

template <> struct get_T1<B_tag> { typedef B::T1 type; };


template <typename Tag>
class util{
    public:
        void print(const typename get_T1<Tag>::type& tobj)
        {
            std::cout << tobj.i << std::endl;
        }

        void print2(const typename get_T2<Tag>::type& tobj) {
            std::cout << tobj.x << std::endl;
        }
};

The above is a lot more elegant because you can declare, alongside your namespace declaration, what types you want to "expose" via a given "tag". You can even make a specialization for the meta-function (e.g., get_T1 and get_T2) to trigger a compile-time error if instantiated. Put simply:

template <> struct get_T2<B_tag> { typedef char[0] B_tag_is_not_a_valid_tag_to_get_T2_functionality; };

Also, this allows for more fine-grained control on your type expositions, because, now, each tag determines a group of types to be exposed to some class/function templates outside that namespace. So, to the broader problem, this is the strategy I would recommend (and similar strategies are very common-place in template-heavy libraries).

Now, to the more specific question:

However it only works if the the type is not in the function signature. So for eg in the code below, namespace B does not have the type 'T2' and 'print2' in Util depends on T2. If I change print2 to 'void print2(typename schemans::T2 t2)' this program does not compile saying 'B' does not have T2 but if I declare it inside the function it compiles fine. could someone please explain the behaviour ?

I am not completely sure about this, because different compilers deal differently about instantiation of templates, especially class templates and their member functions. I am not sure what the standard says about it, but in any case, it varies between compilers.

Basically, the situation is this. When you instantiate a class template like Util for a given template argument (b_NS), the compiler is forced to instantiate a number of things that defined the memory footprint of the class, i.e., non-static data members, virtual member functions, destructor, and a few other things. But most things like (non-virtual) member functions, static member functions, constructors, static data members, etc., are not instantiated onless they are used somewhere. I'm sure you know that already. Where it gets fuzzy is in the no-man's-land between no instantiation and full instantiation (of the definition). Essentially, most compilers will parse the declaration of the member functions and perform the template-argument substitutions for the parameters and result types. And that's where the rules tend to get fuzzy a bit (which is very annoying when trying to maintain a cross-platform template-heavy library). On one extreme (as in some older versions of GCC), some compilers will simply eliminate member functions for which the template-argument substitution fails (in the style of Sfinae (Substitution failure is not an error)). Other compilers will delay the template-argument substitution until there is a call to the function in question (I'm guessing, this is the behaviour you were expecting / hoping for), but I think that is rather rare (can't remember a specific compiler that does that). And other compilers, most notably the microsoft compilers, will be much more aggressive and performing the template-argument subsitutions (and MSVC in particular goes too far, in my opinion) and will throw a tantrum (error) whenever those substitutions fail. In other words, this in an area where compiler-vendors have gone rogue a bit too much, largely due to the fact that early compiler implementations internally dealt with templates in very different ways at a time when people weren't using them as much as today. Now, I think, certain internal implementation issues prevent them from changing those things too much, and the C++ standard remains fairly vague on these points for that same reason.

There are, of course, ways to work around this, for instance, some like this:

namespace detail {

    template <typename schemans>
    void print_impl(typename schemans::T1 tobj) {
        std::cout << tobj.i << std::endl;
    };

    template <typename schemans>
    void print_impl(typename schemans::T2 tobj) {
        std::cout << tobj.x << std::endl;
    };

};

template <typename schemans>
class util{
    public:
        template <typename U>
        void print(U tobj) {
            // Sfinae will work its magic on this:
            detail::print_impl<schemans>(tobj);
        }
};
 
0
 

Thanks a lot mike_2000_17.I did think it was not that elegant but it worked for a first few cases and I didn't think if there was a better way. The technique you have explained definitely looks much better and i'm going to try it out and come back to you if it fits all my cases. I'm new to this sort of template programming so these techniques are not very obvious to me. Also that was a very nice explanation for the template rules and compilers. I had a feeling I was in compiler dependant territory but was not sure about it. Thanks again !

 
0
 

Just curious.. what compiler did you use? I just like to keep tabs of these kinds of compiler-specific issues.

 
0
 

I'm using CC
CC: Sun C++ 5.10 SunOS_sparc Patch 128228-19 2012/05/23

the technique worked brilliantly for me. The fact that I don't have to expose types which are not needed for a particular namespace makes it so much better.

Thanks again !

Question Answered as of 1 Year Ago by mike_2000_17
Isn't it about time forums rewarded their contributors?

Earn rewards points for helping others. Gain kudos. Cash out. Get better answers yourself.

It's as simple as contributing editorial or replying to discussions labeled or OP Kudos

You
This question has already been solved: Start a new discussion instead
Post:
Start New Discussion
Tags Related to this Article