SourceForge.net Logo

[Release b]
<t4m_bug_func_id>
Mon Apr 19 15:41:21 WEST 2004

In the current release of tendra4minix (a), the compilation of the file
func_id.C produces this error

"func_id.C", line 23: Error:
  [ISO 3.2]: The 'inline' function 'A &A::operator<< < double > ( const C < double > & )' has been used but not defined.

This error is generated inside the function check_usage [variable.c] (it is
ERR_basic_odr_inline), because the function identifier has the dspec_used flag
[obj_c/c_class.h] enabled in the field id_storage, but not the dspec_defn flag.
The first flag indicates that the fuction has been used, and the
second flag tells whether it has been defined. In the case of template
instances, to define them it is necessary to add them to the list
pending_templates [instance.c], whose elements are instantiated by the function
clear_templates [instance.c]. This is done by calling define_template
[instance.c] on the template instance. What is happening is that this call is
not being made, and the instance is not being generated.

Let us first summarize what we know about the cause of this bug, and then we
will suggest a solution that does not introduce new bugs. Consider the file
func_id_e.C, almost identical to func_id.C, which is compiled without errors,
and which differs only in that the call to the problematic member function
A::operator<< is made explicitly inside the function operator<<

template <class T>                                  // func_id_e.C, it succeeds
inline A &operator << (B &b, const C<T> &c) { return A(b).operator<<(c); }

instead of using an ambiguous expression, as in func_id.C,

template <class T>                                  // func_id.C, it fails
inline A &operator << (B &b, const C<T> &c) { return A(b) << c; }

It is precisely this ambiguity what produces the first observable difference
between the compilation of these files. First, the compilation of both files
progress in identical manner, invoking make_func_decl [declare.c] to process
the definition of member function A::operator<<, until it is time to analyze
the body of the function operator<<. 

In the case of func_id_e.C, make_func_exp [function.c] is called with
rescan = 0, control reaches the label exp_call_tag in the outer switch, and
the function call_func_templ [function.c] is called.

In the case of func_id.C, the ambiguity in the meaning of the operator << makes
the function binary_overload [operator.c] be called to resolve this ambiguity.
This triggers a complex sequence of calls, among which we find calls to
overload_candidates [operator.c] and resolve_overload [overload.c], which
calls plausible_candidate [overload.c] and viable_candidate [overload.c], 
which in turn calls deduce_args [instance.c] on the functions that are
candidates for resolving the ambiguity.

As it is expected, the call to deduce_args on function operator<< returns
without success, but the call to deduce_args on member function A::operator<<
succeeds, and deduce_args ends up calling inst_func_deduce [instance.c], which
calls to instance_func [instance.c], which generates an instance of 'template
<class T> A::operator<< <T> (const C<T> &)' using the substitution [T := T]
(since we are analyzing the body of the function 'template <class T> operator<<
<T> (B &, const C<T> &)', which is itself a template function).

With this instance, resolve_overload [overload.c] succeeds and returns in the
part commented with the line "Exactly one viable candidate - must be the
winner". Then binary_overload [operator.c] passes this winner to use_func_id
[identifier.c] and apply_func_id [function.c], in this order. The function
apply_func_id finishes the process invoking the constructor MAKE_exp_func_id,
but before that happens, it is use_func_id [identifier.c] who is in charge of
calling define_template [instance.c] to enable the dspec_defn flag in the
template instance. But in the present case, define_template can not complete
its task since we are inside another template: the instance generated is an
"open" instance, and this is detected with a call to the predicate dependent_id
[template.c], which returns 1, and define_template returns in the part that
reads "Ignore template dependent instantiations".

All that is described in the above paragraphs seems correct. The problem is
that no function will try calling define_template when, below, in the main
function, templates are instantiated with the substitution [T := double].
This does happen in the case of func_id_e.C, but not during the compilation of
func_id.C.

In the main function, the part we are interested in begins with the resolution
of the ambiguity posed by the operator <<. This is done by calling
binary_overload [operator.c], which, as we said before, calls
overload_candidates [operator.c] and resolve_overload [overload.c], which calls
viable_candidate [overload.c] (there is only one plausible and viable
candidate), which calls deduce_args [instance.c], which calls inst_func_deduce
[instance.c], which calls instance_func [instance.c], which ends up
instantiating the template function 'template <class T> operator<< <T> (B &,
const C<T> &)' with the substitution [T := double], obtaining 'operator<<
<double> (B &, const C<double> &)'. This instance is returned by
resolve_overload to binary_overload, and binary_overload passes it as an
argument to use_func_id [identifier.c] and apply_func_id [function.c];
use_func_id calls define_template [instance.c], which marks the instance
with the dspec_defn flag. All this happens in identical manner in both files
(func_id.C and func_id_e.C), and it seems correct.

Later, the instance 'operator<< <double> (B &, const C<double> &)' is
effectively generated (since all define_template does is to mark it for
definition, it does not perform the actual definition). This is done in
a composition of calls that begins with clear_templates [instance.c], passes
through copy_template [instance.c], define_templ_member [instance.c] and
bind_template [instance.c], and ends in copy_object [copy.c], which calls
copy_exp [copy.c] to do the copying. This happens in the same way in both
files (func_id.C and func_id_e.C), up until the first invocation of the
function copy_exp [copy.c]. The process of copying is different for each file,
since the body of the function thats being instantiated, 'operator<< <double>
(B &, const C<double> &)', is also different.

We first describe the case that works well.

func_id_e.C:

copy_object, A &operator<< <double> (B &, const C<double> &)
  copy_exp, exp_sequence_tag = 65
    copy_exp, exp_location_tag = 84
    copy_exp, exp_location_tag = 84
      copy_exp, exp_return_stmt_tag = 73
        copy_exp, exp_opn_tag = 81
          apply_nary, op = 342 = lex_func_Hop, operator() [1]
            copy_func_exp, tag = 23 = exp_call_tag
              copy_exp, exp_call_tag = 23
                copy_exp, exp_member_tag = 1
                  rescan_member,template<class T> A &A::operator<<(const C<T>&)
                copy_exp, exp_constr_tag = 49
                  copy_exp, exp_func_id_tag = 22
                    rescan_member, A::A(const B &)
                    copy_func_args, A::A(const B &)
                      copy_exp, exp_address_tag = 19
                        copy_exp, exp_dummy_tag = 87
                      copy_exp, exp_address_tag = 19
                        copy_exp, exp_indir_tag = 17
                          copy_exp, exp_identifier_tag = 0
                            rescan_member, b
            copy_exp_list
              copy_exp, exp_indir_tag = 17
                copy_exp, exp_identifier_tag = 0
                  rescan_member, c
            make_func_exp, exp_call_tag = 23
              rescan_func_id, template<class T> A &A::operator<<(const C<T> &)
                rescan_id
                rescan_member
              resolve_call, template <class T> A &A::operator<< (const C<T> &)
                deduce_args
                  inst_func_deduce
                    instance_func
              use_func_id, A &A::operator<< <double> (const C<double> &)
                define_template
              apply_func_id, A &A::operator<< <double> (const C<double> &)

[1] This operator is probably built by the call to call_func_templ
    [function.c] we mentioned above.

With this, copy_func_exp, apply_nary, all the calls to copy_exp, and
copy_object all return. Then the (member function) instance 'A::operator<<
<double> (const C<double> &)' is effectively generated in a similar way,

clear_templates
  copy_template_list^*
    copy_template
      define_templ_member
        bind_template
          bind_template
            copy_object [2]
        clear_templates
        clear_templates

[2]
copy_object, A &A::operator<< <double> (const C<double> &)
  copy_exp, exp_sequence_tag = 65
    copy_exp, exp_location_tag = 84
    copy_exp, exp_location_tag = 84
      copy_exp, exp_assign_tag = 13
        copy_exp, exp_indir_tag = 17
          copy_exp, exp_add_ptr_tag = 47
            copy_exp, exp_copy_tag = 12
              copy_exp, exp_contents_tag = 18
                copy_exp, exp_identifier_tag = 0
                  rescan_member, A::operator<<
        copy_exp, exp_op_tag = 80
          apply_unary, op = 185 = lex_cast
            make_cast_exp
              ...  // cast.c
              copy_exp, exp_indir_tag = 17
                copy_exp, exp_add_ptr_tag = 47
                  copy_exp, exp_address_tag = 19
                    copy_exp, exp_indir_tag = 17
                      copy_exp, exp_identifier_tag = 0
                        rescan_member, A::operator<<
    copy_exp, exp_location_tag = 84
      copy_exp, exp_return_stmt_tag = 73
        copy_exp, exp_address_tag = 19
          copy_exp, exp_indir_tag = 17
            copy_exp, exp_copy_tag = 12
              copy_exp, exp_contents_tag = 18
                copy_exp, exp_identifier_tag = 0
                  rescan_member, A::operator<<

With this all calls to copy_exp return up to copy_object.

Now let us see the case that does not work.

func_id.C:

copy_object, A &operator<< <double> (B &, const C<double> &)
  copy_exp, exp_sequence_tag = 65
    copy_exp, exp_location_tag = 84
    copy_exp, exp_decl_stmt_tag = 67
      copy_exp, exp_sequence_tag = 65
        copy_exp, exp_location_tag = 84
          copy_exp, exp_return_stmt_tag = 73
            copy_exp, exp_address_tag = 19
              copy_exp, exp_indir_tag = 17
                copy_exp, exp_func_id_tag = 22 [3]
                  rescan_member, A &A::operator<< <T> (const C<T> &)
                    rescan_member,template<class T>A&A::operator<<(const C<T>&)
                      apply_template, template <class T> A &A::operator<<
                        apply_func_templ, template <class T> A &A::operator<<
                          instance_func
                  copy_func_args, A &A::operator<< <double> (const C<double> &)
                    copy_exp, exp_address_tag = 19
                      copy_exp, exp_init_tag = 14
                        copy_exp, exp_constr_tag = 49
                          copy_exp, exp_func_id_tag = 22
                            rescan_member, A::A(const B &)
                            copy_func_args, A::A(const B &)
                              copy_exp, exp_address_tag = 19
                                copy_exp, exp_dummy_tag = 87
                              copy_exp, exp_address_tag = 19
                                copy_exp, exp_indir_tag = 17
                                  copy_exp, exp_identifier_tag = 0
                                    rescan_member, b
                    copy_exp, exp_address_tag = 19
                      copy_exp, exp_indir_tag = 17
                        copy_exp, exp_identifier_tag = 0
                          rescan_member, c

[3] This expression is probably built by the call to apply_func_id [function.c]
    we mentioned above.

With this, all calls up to copy_object return.

Now we are going to suggest a fix for this problem. Observe the essential
difference between the call trees of both files at the moment of instantiating
the member function 'template <class T> A &A::operator<<' with [T := double],

func_id_e.C: ...
            make_func_exp, exp_call_tag = 23
              rescan_func_id, template<class T> A &A::operator<<(const C<T> &)
                rescan_id
                rescan_member
              resolve_call, template <class T> A &A::operator<< (const C<T> &)
                deduce_args
                  inst_func_deduce
                    instance_func
              use_func_id, A &A::operator<< <double> (const C<double> &)
                define_template
              apply_func_id, A &A::operator<< <double> (const C<double> &)

func_id.C: ...
                copy_exp, exp_func_id_tag = 22
                  rescan_member, A &A::operator<< <T> (const C<T> &)
                    rescan_member,template<class T>A&A::operator<<(const C<T>&)
                      apply_template, template <class T> A &A::operator<<
                        apply_func_templ, template <class T> A &A::operator<<
                          instance_func
                  copy_func_args, A &A::operator<< <double> (const C<double> &)
                    ...

Clearly, the call tree for func_id.C lacks a call to define_template
[instance.c]. It also lacks the call to apply_func_id [function.c], but I think
that this is not necessary, since the call was made on the "open" instance
(with [T := T]) 'A::operator<< <T> (const C<T> &)', during the overload
resolution that took place inside the template function 'template <class T>
operator<< <T> (B &, const C<T> &)'. This call to apply_func_id must have
succeeded, and the proof of it is that in the present instantiation (with
[T := double]), the difference of treatment in the case of func_id.C is
precisely due to the expression exp_func_id_tag that was built by invoking the
constructor MAKE_exp_func_id.

The invocation of use_func_id [identifier.c] is a different matter; it is
possible that it must be repeated inside rescan_member [copy.c], but we are
going to suppose that the compiler is not that bad, and that the original
authors did know what they were doing, and let us see if we can fix this
problem calling define_template [instance.c] from rescan_member [copy.c].
We do it in this way, changing

		    id = apply_template ( tid, args, 0, 0 ) ;
		    ns = NULL_nspace ;

for

		    id = apply_template ( tid, args, 0, 0 ) ;
		    define_template ( id, 0 ) ;
		    ds = DEREF_dspec ( id_storage ( id ) ) ;
		    ns = NULL_nspace ;

This solution works for func_id.C and for its variants func_id_f.C and
func_id_t.C (see the notes below).

PS. See <t4m_bug_nonexport> y <t4m_bug_usage>, that make necessary further
modifications to the code of rescan_member.

Notes.

[V] ¿Why do virtual functions need a special treatment in use_func_id?
    PS. The official answer [identifier.c] is: "Note that use_func_id does not
        mark virtual functions as having been used because the actual function
        called can only be determined at run-time." See also the next note.

[V] Try making A::operator<< a virtual function.
    PS. It is not possible. ISO 14.5.2, par. 3, «A member function template
        shall not be virtual».

[V] Try making A::operator<< a non-member function.
    PS. The file is func_id_f.C. The behaviour of the compiler is similar to
        that produced by the original func_id.C, and the bug also gets fixed
        with our modification.

[V] Try using a member function template of a class template.
    PS. The file is func_id_t.C. The behaviour of the compiler is similar to
        that produced by the original func_id.C, and the bug also gets fixed
        with our modification.