Circular Class Template Friendship

Discussion in 'C++' started by Thomas Matthews, Jul 24, 2004.

  1. Hi,

    I am converting my table and record classes into templates.
    My issue is the syntax of declaring a friend class within
    the template. I have searched the C++ FAQ Lite (web),
    the C++ newsgroups, "Thinking In C++" to no avail.


    Background
    ----------
    My table is a collection of <integer, string> pairs, in
    which the string is a fixed width that is specialized.
    One specialization of the table may have a 32 length
    string and another 64. The table is also a Singleton.

    The record class is of the form <integer, string>.
    The integer portion, the primary key, is hidden from
    public usage, so that the class behaves like a string.
    When the class is written as a field of a record, the
    integer portion will be written out.

    Part of the interface is for the table to be a friend
    of the record. This allows the table to set the primary
    key without giving access or knowledge of the primary
    key to the general public.


    The Code
    ---------
    Here is my code:
    // File Name_Id_Table.hpp

    template <typename Record_Class,
    const char * TABLE_NAME>
    class Name_Id_Table
    {
    //...
    public:
    void load_from_table(Record_Class& rc)
    {
    rc.id = get_id_from_table();
    rc.name = get_name_from_table();
    }
    };

    // File Name_Id.hpp
    #include <string>
    using std::string

    template <int MAX_STRING_WIDTH>
    class Name_Id
    {
    // ...

    /* The following line is what I need help with */
    template <> friend class<Name_ID, ????> Name_Id_Table;

    private:
    int id;
    string name;
    };


    // File main.cpp
    #include "Name_Id_Table.hpp"
    #include "Name_Id.hpp"

    const char * TITLE_TABLE_NAME = "Titles";

    /* Here is another issue I'm having problems with.
    * I want to declare the types but am having syntax
    * issues.
    */
    typedef Name_Id<64> Title;
    typedef Name_Id_Table<Title, TITLE_TABLE_NAME> Title_Table;

    int main(void)
    {
    Title t;
    Title_Table table;

    table.load_from_table(t);

    return EXIT_SUCCESS;
    }



    In the database, I will have three Name-ID tables:
    Title, Author, Publisher. The string widths will
    differ and they will have different table names.
    Otherwise they have the same functionality. Each
    table is a singleton; the Title table will not
    contain author or publisher entries. Similarly
    with Author and Publisher tables.


    --
    Thomas Matthews

    C++ newsgroup welcome message:
    http://www.slack.net/~shiva/welcome.txt
    C++ Faq: http://www.parashift.com/c -faq-lite
    C Faq: http://www.eskimo.com/~scs/c-faq/top.html
    alt.comp.lang.learn.c-c++ faq:
    http://www.raos.demon.uk/acllc-c /faq.html
    Other sites:
    http://www.josuttis.com -- C++ STL Library book
    http://www.sgi.com/tech/stl -- Standard Template Library
    Thomas Matthews, Jul 24, 2004
    #1
    1. Advertising

  2. "Thomas Matthews" <> wrote...
    > I am converting my table and record classes into templates.
    > My issue is the syntax of declaring a friend class within
    > the template. I have searched the C++ FAQ Lite (web),
    > the C++ newsgroups, "Thinking In C++" to no avail.
    >
    >
    > Background
    > ----------
    > My table is a collection of <integer, string> pairs, in
    > which the string is a fixed width that is specialized.
    > One specialization of the table may have a 32 length
    > string and another 64. The table is also a Singleton.
    >
    > The record class is of the form <integer, string>.
    > The integer portion, the primary key, is hidden from
    > public usage, so that the class behaves like a string.
    > When the class is written as a field of a record, the
    > integer portion will be written out.
    >
    > Part of the interface is for the table to be a friend
    > of the record. This allows the table to set the primary
    > key without giving access or knowledge of the primary
    > key to the general public.
    >
    >
    > The Code
    > ---------
    > Here is my code:
    > // File Name_Id_Table.hpp
    >
    > template <typename Record_Class,
    > const char * TABLE_NAME>
    > class Name_Id_Table
    > {
    > //...
    > public:
    > void load_from_table(Record_Class& rc)
    > {
    > rc.id = get_id_from_table();
    > rc.name = get_name_from_table();
    > }
    > };
    >
    > // File Name_Id.hpp
    > #include <string>
    > using std::string


    Ugh! Yuck!!! Never put a using declaration into a global scope in
    a header. NEVER! There is no reason for it to be there. If you
    are so inclined to save some typing and use 'string' instead of
    'std::string' in your class definition, put the 'using' there, inside
    that class definition. Hide your 'using's as deep as possible.

    >
    > template <int MAX_STRING_WIDTH>
    > class Name_Id
    > {
    > // ...
    >
    > /* The following line is what I need help with */
    > template <> friend class<Name_ID, ????> Name_Id_Table;


    What are you trying to accomplish? All possible Name_Id_Table
    instantiations with the same Name_ID should be friends or only
    the one that has a particular TABLE_NAME?

    At this point, it is still possible to have Name_Id_Table template
    specialised on the same Name_Id class, but with different table
    names. There is nothing in your code that prevents that.

    So, do you want a particular fully specialised Name_Id_Table to
    be a friend? Then you have to give the address of a constant
    character here. Which one? You decide. The easiest solution
    would be to give Name_Id an extra argument and pass it along to
    the friend class declaration.

    >
    > private:
    > int id;
    > string name;
    > };
    >
    >
    > // File main.cpp
    > #include "Name_Id_Table.hpp"
    > #include "Name_Id.hpp"
    >
    > const char * TITLE_TABLE_NAME = "Titles";


    No, that has to be 'extern'.

    >
    > /* Here is another issue I'm having problems with.
    > * I want to declare the types but am having syntax
    > * issues.


    WHAT syntax issues?

    > */
    > typedef Name_Id<64> Title;
    > typedef Name_Id_Table<Title, TITLE_TABLE_NAME> Title_Table;
    >
    > int main(void)
    > {
    > Title t;
    > Title_Table table;
    >
    > table.load_from_table(t);
    >
    > return EXIT_SUCCESS;
    > }
    >
    >
    >
    > In the database, I will have three Name-ID tables:
    > Title, Author, Publisher. The string widths will
    > differ and they will have different table names.


    You can create a templated "table of widths" and only worry
    about passing the name to your 'Name_Id' template and to the
    'Name_Id_Table' template:
    ---------------------------------------------------------
    template<const char* name> struct Name_Id_Aux_Info { enum { MaxWidth }; };
    template<typename T, const char* name> class Name_Id_Table;

    template<const char* name> class Name_Id {
    enum { MAX_STRING_WIDTH =
    Name_Id_Aux_Info<name>::MaxWidth };

    int foo() { return 42; } // private
    friend class Name_Id_Table<Name_Id, name>;
    };

    template<typename T, const char* name> class Name_Id_Table
    {
    public:
    int bar(T& t) { return t.foo(); }
    };

    extern const char TITLE[] = "Title";
    extern const char AUTHOR[] = "Author";
    extern const char PUBLISHER[] = "Publisher";

    template<> struct Name_Id_Aux_Info<TITLE> { enum { MaxWidth = 32 }; };
    template<> struct Name_Id_Aux_Info<AUTHOR> { enum { MaxWidth = 64 }; };
    template<> struct Name_Id_Aux_Info<PUBLISHER> { enum { MaxWidth = 96 }; };

    typedef Name_Id<TITLE> id_Title;
    typedef Name_Id_Table<id_Title, TITLE> id_Title_Table;

    int main()
    {
    id_Title idt;
    id_Title_Table idtt;

    idtt.bar(idt);

    return 0;
    }

    > Otherwise they have the same functionality. Each
    > table is a singleton; the Title table will not
    > contain author or publisher entries. Similarly
    > with Author and Publisher tables.


    If you tie them by the name only, you have a chance, I believe.
    Victor Bazarov, Jul 24, 2004
    #2
    1. Advertising

  3. Thomas Matthews wrote in news: in
    comp.lang.c++:

    >
    > Hi,
    >




    #include <cstdlib>
    //^^^^^^^^^^^^^^^^ EXIT_SUCCESS

    > template <typename Record_Class,
    > const char * TABLE_NAME>
    > class Name_Id_Table
    > {
    > public:
    > void load_from_table(Record_Class& rc)
    > {
    > // [snipped]
    > }
    > };
    >
    > // File Name_Id.hpp
    > #include <string>


    using std::string;
    // ^^^^^^missing ;

    > template <int MAX_STRING_WIDTH>
    > class Name_Id
    > {
    > // ...
    >
    > /* The following line is what I need help with */

    template < typename, const char * >
    friend class Name_Id_Table;

    /* ^^^^^^^^^^^^^^^^^^^^^^^^

    friend-ship needs to be the class template or an explicit
    specialization, partial-specialization's not allowed.
    */

    > private:
    > int id;
    > string name;
    > };
    >
    >

    extern char const TITLE_TABLE_NAME[] = "Titles";
    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    >
    > /* Here is another issue I'm having problems with.
    > * I want to declare the types but am having syntax
    > * issues.


    See Above ^^^^^^^

    > */
    > typedef Name_Id<64> Title;
    > typedef Name_Id_Table<Title, TITLE_TABLE_NAME> Title_Table;
    >
    > int main(void)
    > {
    > Title t;
    > Title_Table table;
    >
    > table.load_from_table(t);
    >
    > return EXIT_SUCCESS;
    > }



    BTW - Most people prefer lowercase or MixedCase for non-macro
    identifiers, for example TABLE_NAME and TITLE_TABLE_NAME above.

    HTH.

    Rob.
    --
    http://www.victim-prime.dsl.pipex.com/
    Rob Williscroft, Jul 24, 2004
    #3
  4. Victor Bazarov wrote in news:HzwMc.21062$eM2.20260@attbi_s51 in
    comp.lang.c++:

    >> // File Name_Id.hpp
    >> #include <string>
    >> using std::string

    >
    > Ugh! Yuck!!! Never put a using declaration into a global scope in
    > a header. NEVER! There is no reason for it to be there.


    Can't disagree with that.

    > If you
    > are so inclined to save some typing and use 'string' instead of
    > 'std::string' in your class definition, put the 'using' there, inside
    > that class definition. Hide your 'using's as deep as possible.
    >


    #include <string>

    struct x
    {
    using std::string;
    };

    test.cpp(5) : error C2886: 'std::string' : symbol cannot be used in
    a member using-declaration

    A typedef does work though:

    #include <string>

    struct x
    {
    typedef std::string string;
    };

    Rob.
    --
    http://www.victim-prime.dsl.pipex.com/
    Rob Williscroft, Jul 24, 2004
    #4
  5. Victor Bazarov wrote:
    > "Thomas Matthews" <> wrote...
    >
    >>// File Name_Id.hpp
    >>#include <string>
    >>using std::string

    >
    >
    > Ugh! Yuck!!! Never put a using declaration into a global scope in
    > a header. NEVER! There is no reason for it to be there. If you
    > are so inclined to save some typing and use 'string' instead of
    > 'std::string' in your class definition, put the 'using' there, inside
    > that class definition. Hide your 'using's as deep as possible.
    >
    >

    I've decided to move it out of the class because it doesn't
    compile when inside the class. See Rob's response to your article.

    [snip]
    >>In the database, I will have three Name-ID tables:
    >>Title, Author, Publisher. The string widths will
    >>differ and they will have different table names.

    >
    >
    > You can create a templated "table of widths" and only worry
    > about passing the name to your 'Name_Id' template and to the
    > 'Name_Id_Table' template:
    > ---------------------------------------------------------
    > template<const char* name> struct Name_Id_Aux_Info { enum { MaxWidth }; };
    > template<typename T, const char* name> class Name_Id_Table;
    >
    > template<const char* name> class Name_Id {
    > enum { MAX_STRING_WIDTH =
    > Name_Id_Aux_Info<name>::MaxWidth };
    >
    > int foo() { return 42; } // private
    > friend class Name_Id_Table<Name_Id, name>;

    At this point, my compiler, Borland Builder 6.0, gives me this
    error:
    E2299 Cannot generate template specialization from
    'Name_Id_Table<T, name>'

    Although Gnu G++ 3.3.1 compiles without any warnings or errors.

    > };
    >
    > template<typename T, const char* name> class Name_Id_Table
    > {
    > public:
    > int bar(T& t) { return t.foo(); }
    > };
    >
    > extern const char TITLE[] = "Title";
    > extern const char AUTHOR[] = "Author";
    > extern const char PUBLISHER[] = "Publisher";
    >
    > template<> struct Name_Id_Aux_Info<TITLE> { enum { MaxWidth = 32 }; };
    > template<> struct Name_Id_Aux_Info<AUTHOR> { enum { MaxWidth = 64 }; };
    > template<> struct Name_Id_Aux_Info<PUBLISHER> { enum { MaxWidth = 96 }; };
    >
    > typedef Name_Id<TITLE> id_Title;
    > typedef Name_Id_Table<id_Title, TITLE> id_Title_Table;
    >
    > int main()
    > {
    > id_Title idt;
    > id_Title_Table idtt;
    >
    > idtt.bar(idt);
    >
    > return 0;
    > }


    Is Gnu correct in this case or Borland?
    {Generates same error with BCC32 version 5.6}


    --
    Thomas Matthews

    C++ newsgroup welcome message:
    http://www.slack.net/~shiva/welcome.txt
    C++ Faq: http://www.parashift.com/c -faq-lite
    C Faq: http://www.eskimo.com/~scs/c-faq/top.html
    alt.comp.lang.learn.c-c++ faq:
    http://www.raos.demon.uk/acllc-c /faq.html
    Other sites:
    http://www.josuttis.com -- C++ STL Library book
    Thomas Matthews, Jul 26, 2004
    #5
  6. Thomas Matthews wrote:
    > Victor Bazarov wrote:
    >> You can create a templated "table of widths" and only worry
    >> about passing the name to your 'Name_Id' template and to the
    >> 'Name_Id_Table' template:
    >> ---------------------------------------------------------
    >> template<const char* name> struct Name_Id_Aux_Info { enum { MaxWidth
    >> }; };
    >> template<typename T, const char* name> class Name_Id_Table;
    >>
    >> template<const char* name> class Name_Id {
    >> enum { MAX_STRING_WIDTH =
    >> Name_Id_Aux_Info<name>::MaxWidth };
    >>
    >> int foo() { return 42; } // private
    >> friend class Name_Id_Table<Name_Id, name>;

    >
    > At this point, my compiler, Borland Builder 6.0, gives me this
    > error:
    > E2299 Cannot generate template specialization from
    > 'Name_Id_Table<T, name>'
    >
    > Although Gnu G++ 3.3.1 compiles without any warnings or errors.
    >
    >> };
    >>
    >> template<typename T, const char* name> class Name_Id_Table
    >> {
    >> public:
    >> int bar(T& t) { return t.foo(); }
    >> };
    >>
    >> extern const char TITLE[] = "Title";
    >> extern const char AUTHOR[] = "Author";
    >> extern const char PUBLISHER[] = "Publisher";
    >>
    >> template<> struct Name_Id_Aux_Info<TITLE> { enum { MaxWidth = 32 }; };
    >> template<> struct Name_Id_Aux_Info<AUTHOR> { enum { MaxWidth = 64 }; };
    >> template<> struct Name_Id_Aux_Info<PUBLISHER> { enum { MaxWidth = 96
    >> }; };
    >>
    >> typedef Name_Id<TITLE> id_Title;
    >> typedef Name_Id_Table<id_Title, TITLE> id_Title_Table;
    >>
    >> int main()
    >> {
    >> id_Title idt;
    >> id_Title_Table idtt;
    >>
    >> idtt.bar(idt);
    >>
    >> return 0;
    >> }

    >
    >
    > Is Gnu correct in this case or Borland?
    > {Generates same error with BCC32 version 5.6}


    The code I posted compiles fine with Comeau online and with VC++ v7.1
    which kind of suggests that the message Borland compiler emits is not
    an indication of the error in the code but rather the admission of
    BCBuilder's impotency.

    Victor
    Victor Bazarov, Jul 26, 2004
    #6
  7. Victor Bazarov wrote:

    > Thomas Matthews wrote:
    >
    >> Victor Bazarov wrote:
    >>
    >>> You can create a templated "table of widths" and only worry
    >>> about passing the name to your 'Name_Id' template and to the
    >>> 'Name_Id_Table' template:
    >>> ---------------------------------------------------------
    >>> template<const char* name> struct Name_Id_Aux_Info { enum { MaxWidth
    >>> }; };
    >>> template<typename T, const char* name> class Name_Id_Table;
    >>>
    >>> template<const char* name> class Name_Id {
    >>> enum { MAX_STRING_WIDTH =
    >>> Name_Id_Aux_Info<name>::MaxWidth };
    >>>
    >>> int foo() { return 42; } // private
    >>> friend class Name_Id_Table<Name_Id, name>;

    >>
    >>
    >> At this point, my compiler, Borland Builder 6.0, gives me this
    >> error:
    >> E2299 Cannot generate template specialization from
    >> 'Name_Id_Table<T, name>'
    >>
    >> Although Gnu G++ 3.3.1 compiles without any warnings or errors.
    >>
    >>> };
    >>>
    >>> template<typename T, const char* name> class Name_Id_Table
    >>> {
    >>> public:
    >>> int bar(T& t) { return t.foo(); }
    >>> };
    >>>
    >>> extern const char TITLE[] = "Title";
    >>> extern const char AUTHOR[] = "Author";
    >>> extern const char PUBLISHER[] = "Publisher";
    >>>
    >>> template<> struct Name_Id_Aux_Info<TITLE> { enum { MaxWidth = 32 }; };
    >>> template<> struct Name_Id_Aux_Info<AUTHOR> { enum { MaxWidth = 64 }; };
    >>> template<> struct Name_Id_Aux_Info<PUBLISHER> { enum { MaxWidth = 96
    >>> }; };
    >>>
    >>> typedef Name_Id<TITLE> id_Title;
    >>> typedef Name_Id_Table<id_Title, TITLE> id_Title_Table;
    >>>
    >>> int main()
    >>> {
    >>> id_Title idt;
    >>> id_Title_Table idtt;
    >>>
    >>> idtt.bar(idt);
    >>>
    >>> return 0;
    >>> }

    >>
    >>
    >>
    >> Is Gnu correct in this case or Borland?
    >> {Generates same error with BCC32 version 5.6}

    >
    >
    > The code I posted compiles fine with Comeau online and with VC++ v7.1
    > which kind of suggests that the message Borland compiler emits is not
    > an indication of the error in the code but rather the admission of
    > BCBuilder's impotency.
    >
    > Victor


    I posted your program in a Borland newsgroup and they suggested
    the following modification:

    template<const char* name> class Name_Id {
    enum { MAX_STRING_WIDTH =
    Name_Id_Aux_Info<name>::MaxWidth };

    int foo() { return 42; } // private
    friend class Name_Id_Table<Name_Id<name>, name>;
    /* ^^^^^^ */

    This makes sense, but I don't know why the other compilers
    don't require it.

    When I make the change, the program compiles fine with
    the Borland compiler.

    --
    Thomas Matthews

    C++ newsgroup welcome message:
    http://www.slack.net/~shiva/welcome.txt
    C++ Faq: http://www.parashift.com/c -faq-lite
    C Faq: http://www.eskimo.com/~scs/c-faq/top.html
    alt.comp.lang.learn.c-c++ faq:
    http://www.comeaucomputing.com/learn/faq/
    Other sites:
    http://www.josuttis.com -- C++ STL Library book
    Thomas Matthews, Jul 28, 2004
    #7
  8. Thomas Matthews wrote:
    > Victor Bazarov wrote:
    >
    >> Thomas Matthews wrote:
    >>
    >>> Victor Bazarov wrote:
    >>>
    >>>> You can create a templated "table of widths" and only worry
    >>>> about passing the name to your 'Name_Id' template and to the
    >>>> 'Name_Id_Table' template:
    >>>> ---------------------------------------------------------
    >>>> template<const char* name> struct Name_Id_Aux_Info { enum { MaxWidth
    >>>> }; };
    >>>> template<typename T, const char* name> class Name_Id_Table;
    >>>>
    >>>> template<const char* name> class Name_Id {
    >>>> enum { MAX_STRING_WIDTH =
    >>>> Name_Id_Aux_Info<name>::MaxWidth };
    >>>>
    >>>> int foo() { return 42; } // private
    >>>> friend class Name_Id_Table<Name_Id, name>;
    >>>
    >>>
    >>>
    >>> At this point, my compiler, Borland Builder 6.0, gives me this
    >>> error:
    >>> E2299 Cannot generate template specialization from
    >>> 'Name_Id_Table<T, name>'
    >>>
    >>> Although Gnu G++ 3.3.1 compiles without any warnings or errors.
    >>>
    >>>> };
    >>>>
    >>>> template<typename T, const char* name> class Name_Id_Table
    >>>> {
    >>>> public:
    >>>> int bar(T& t) { return t.foo(); }
    >>>> };
    >>>>
    >>>> extern const char TITLE[] = "Title";
    >>>> extern const char AUTHOR[] = "Author";
    >>>> extern const char PUBLISHER[] = "Publisher";
    >>>>
    >>>> template<> struct Name_Id_Aux_Info<TITLE> { enum { MaxWidth = 32 }; };
    >>>> template<> struct Name_Id_Aux_Info<AUTHOR> { enum { MaxWidth = 64 }; };
    >>>> template<> struct Name_Id_Aux_Info<PUBLISHER> { enum { MaxWidth = 96
    >>>> }; };
    >>>>
    >>>> typedef Name_Id<TITLE> id_Title;
    >>>> typedef Name_Id_Table<id_Title, TITLE> id_Title_Table;
    >>>>
    >>>> int main()
    >>>> {
    >>>> id_Title idt;
    >>>> id_Title_Table idtt;
    >>>>
    >>>> idtt.bar(idt);
    >>>>
    >>>> return 0;
    >>>> }
    >>>
    >>>
    >>>
    >>>
    >>> Is Gnu correct in this case or Borland?
    >>> {Generates same error with BCC32 version 5.6}

    >>
    >>
    >>
    >> The code I posted compiles fine with Comeau online and with VC++ v7.1
    >> which kind of suggests that the message Borland compiler emits is not
    >> an indication of the error in the code but rather the admission of
    >> BCBuilder's impotency.
    >>
    >> Victor

    >
    >
    > I posted your program in a Borland newsgroup and they suggested
    > the following modification:
    >
    > template<const char* name> class Name_Id {
    > enum { MAX_STRING_WIDTH =
    > Name_Id_Aux_Info<name>::MaxWidth };
    >
    > int foo() { return 42; } // private
    > friend class Name_Id_Table<Name_Id<name>, name>;
    > /* ^^^^^^ */
    >
    > This makes sense, but I don't know why the other compilers
    > don't require it.


    You should ask Borland why they made it so that _their_ compiler _does_
    require it.

    The 'Name_Id' is the name of the template that is injected into the
    class template scope. Every time you [try to] instantiate Name_Id,
    it should become a concrete class, which then should specify which
    specialisation of Name_Id_Table is used without the need to specify
    the Name_Id's template argument explicitly, IMO.

    [I am too lazy to look for a quote from the Standard.]

    >
    > When I make the change, the program compiles fine with
    > the Borland compiler.
    >


    I'm glad you got it resolved.

    Victor
    Victor Bazarov, Jul 28, 2004
    #8
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Kiuhnm
    Replies:
    16
    Views:
    741
    Jonathan Mcdougall
    Jan 3, 2005
  2. Kai Wu
    Replies:
    2
    Views:
    300
    Matt Bitten
    Mar 2, 2005
  3. christopher diggins
    Replies:
    16
    Views:
    749
    Pete Becker
    May 4, 2005
  4. Mr Dyl
    Replies:
    4
    Views:
    384
  5. Qi
    Replies:
    4
    Views:
    763
Loading...

Share This Page