[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.