15

I am playing a bit with static polymorphism, I'm calling a function which internally calls the "right" specialized function depending on the type of the initial argument (basically I'm doing tagging). Here is the code:

#include <iostream>

using namespace std;

// tags
struct tag1{}; 
struct tag2{}; 

// the compliant types, all should typedef tag_type
struct my_type1
{
    using tag_type = tag1;
};
struct my_type2
{
    using tag_type = tag2;
};

// static dispatch via tagging
template <typename T>
void f(T) 
{
    cout << "In void f<typename T>(T)" << endl;

    // why can I call f_helper without forward definition?!?        
    f_helper(typename T::tag_type{}); 
}

int main()
{
    my_type1 type1;
    my_type2 type2;

    // how does f below knows about f_helper ?!?!
    // even after instantiation f_helper shouldn't be visible!

    f(type1); 
    f(type2);
}

// helper functions
void f_helper(tag1) 
{
    cout << "f called with my_type1" << endl;
}
void f_helper(tag2)
{
    cout << "f called with my_type2" << endl;
}

So, f(T) is called with a parameter my_type1 or my_type2 that internally must typedef tag_type with the appropriate tag tag1/tag2. Depending on this internal tag_type, the "right" wrapper is then called, and this decision is made of course at compile time. Now I really don't understand why this code IS working? Why don't we need to forward-declare f_helper? I first had the wrappers defined before main (and after f), and I though ok, this makes sense, you don't need to forward declare because the compiler instantiate the template only when f(type1); is called (in main()), before it doesn't know the type T, so at the time of instantiation the compiler knows f_wrapper.

But as you see, even if I declare the wrappers AFTER main(), the code still works. Why is this happening? I guess the question is a bit strange, asking why a code works :)


EDIT

The code continues to compile even in gcc5 and gcc HEAD 6.0.0.

vsoftco
  • 55,410
  • 12
  • 139
  • 252
  • 2
    well, I'm stumped: [GCC](http://coliru.stacked-crooked.com/a/b03630bcf141b805) accepts it, as does [Clang](http://coliru.stacked-crooked.com/a/296c076910ae4aeb), and [Visual Studio](http://rextester.com/SPL54741) – Mooing Duck Aug 07 '14 at 17:43
  • @MooingDuck :) yeah me too – vsoftco Aug 07 '14 at 18:12
  • Datapoint: GCC and clang fail to compile this if the call to the function is fully qualified: `::f_helper(typename T::tag_type{});`. Visual Studio continues to compile it. – Mooing Duck Aug 07 '14 at 18:20
  • @Mooing - that VS still compiles the qualfied call is likely due to its non-compliant two phase lookup. – Martin Ba Aug 07 '14 at 19:08
  • @MartinBa: I agree, I think that's a bug in VS. – Mooing Duck Aug 07 '14 at 20:29
  • Thanks everyone for the comments and efforts of looking into the standard, I don't know which answer to accept as the issue still seems to be still debatable. I will keep the question open for a few more days and try some standardese by myself :). – vsoftco Aug 08 '14 at 18:10

3 Answers3

9

f_helper(typename T::tag_type{}) is a type-dependent expression because T::tag_type is a dependent type. This means that f_helper doesn't need to be visible until f<T> is instantiated due to two phase lookup.

EDIT: I'm pretty sure that this is actually undefined behaviour. If we look at 14.6.4.2 [temp.dep.candidate] we see this passage:

For a function call that depends on a template parameter, the candidate functions are found using the usual lookup rules (3.4.1, 3.4.2, 3.4.3) except that:

— For the part of the lookup using unqualified name lookup (3.4.1) or qualified name lookup (3.4.3), only function declarations from the template definition context are found.

— For the part of the lookup using associated namespaces (3.4.2), only function declarations found in either the template definition context or the template instantiation context are found.

If the function name is an unqualified-id and the call would be ill-formed or would find a better match had the lookup within the associated namespaces considered all the function declarations with external linkage introduced in those namespaces in all translation units, not just considering those declarations found in the template definition and template instantiation contexts, then the program has undefined behavior.

The last paragraph to me indicates this is undefined behaviour. The function call that depends on a template parameter here is f_helper(typename T::tag_type{}). f_helper isn't visible when f is instantiated, but it would be if we performed name lookup after all translation units have been compiled.

Simple
  • 13,992
  • 2
  • 47
  • 47
  • 4
    But isn't `f` instantiated *before* `f_helper` is visible? – David G Aug 07 '14 at 17:25
  • @0x499602D2, exactly, that's the point I'm ABSOLUTELY confused about. – vsoftco Aug 07 '14 at 17:34
  • @0x499602D2 I think it's undefined behaviour after having a closer look. See my edit. – Simple Aug 07 '14 at 17:44
  • Failure to find any matching function declaration at all means an ill-formed program, not undefined behavior. – aschepler Aug 07 '14 at 17:57
  • 2
    @aschepler: That's what I thought too but... "For a function call that depends on a template parameter...If the function name is an unqualified-id and the call would be ill-formed .... then the program has undefined behavior." – Mooing Duck Aug 07 '14 at 18:14
  • @aschepler: indeed, if I fully qualify the function call, I get the expected error: http://coliru.stacked-crooked.com/a/a9cd2f502e8fca7a – Mooing Duck Aug 07 '14 at 18:14
  • It's not UB, because the point of definition is not a point of instantiation. The function only has to be declared before the first point of instantiation (in every TU). – Potatoswatter Aug 08 '14 at 08:43
  • @MooingDuck Remember, qualification disables ADL. – Potatoswatter Aug 08 '14 at 08:44
  • @Potatoswatter the point of instantiation is before `main`, no? And `f_helper` is declared afer `main`. – Simple Aug 08 '14 at 09:11
4

I agree, the code is ill-formed. I'm surprised neither g++ nor clang++ has even a warning about this.

14.6.2/1:

In an expression of the form:

  • postfix-expression ( expression-list [opt] )

where the postfix-expression is an id-expression, the id-expression denotes a dependent name if any of the expressions in the expression-list is a type-dependent expression (14.6.2.2) or if the unqualified-id of the id-expression is a template-id in which any of the template arguments depends on a template parameter. ... Such names are unbound and are looked up at the point of the template instantiation (14.6.4.1) in both the context of the template definition and the context of the point of instantiation.

[f_helper is a postfix-expression and id-expression, and typename T::tag_type{} is type-dependent, so f_helper is a dependent name.]

14.6.4/1:

In resolving dependent names, names from the following sources are considered:

  • Declarations that are visible at the point of definition of the template.

  • Declarations from namespaces associated with the types of the function arguments both from the instantiation context (14.6.4.1) and from the definition context.

14.6.4.1/6:

The instantiation context of an expression that depends on the template arguments is the set of declarations with external linkage declared prior to the point of instantiation of the template specialization in the same translation unit.

14.6.4.2/1:

For a function call that depends on a template parameter, the candidate functions are found using the usual lookup rules (3.4.1, 3.4.2, 3.4.3) except that:

  • For the part of the lookup using unqualified name lookup (3.4.1) or qualified name lookup (3.4.3), only function declarations from the template definition context are found.

  • For the part of the lookup using associated namespaces (3.4.2), only function declarations found in either the template definition context or the template instantiation context are found.

Community
  • 1
  • 1
aschepler
  • 70,891
  • 9
  • 107
  • 161
  • Also note: http://coliru.stacked-crooked.com/a/a9cd2f502e8fca7a. You've also removed the part of 14.6.4.2 that explicitly calls out this situation as undefined behavior. – Mooing Duck Aug 07 '14 at 18:15
  • @MooingDuck: That sentence does not apply. The theoretical program-global name lookup would not "find a better match" than the actual lookup rules, because the actual lookup rules do not find any function declaration. – aschepler Aug 07 '14 at 20:15
  • I disagree: "If the __function name is an unqualified-id and the call would be ill-formed or__ would find a better match"... – Mooing Duck Aug 07 '14 at 20:30
  • But the call would not be ill-formed, with all function declarations considered. – aschepler Aug 07 '14 at 20:32
3

The call to f_helper(typename T::tag_type{}); is dependent on the template parameter T, so the name f_helper need not be visible until the point of instantiation of f<T> (due to two phase name lookup).

I believe the code works because implementations are allowed to delay the point of instantiation of function templates until the end of the translation unit, at which time the definitions for f_helper are available.

N3936 §14.6.4.1/8 [temp.point]

A specialization for a function template, a member function template, or of a member function or static data member of a class template may have multiple points of instantiations within a translation unit, and in addition to the points of instantiation described above, for any such specialization that has a point of instantiation within the translation unit, the end of the translation unit is also considered a point of instantiation. A specialization for a class template has at most one point of instantiation within a translation unit. A specialization for any template may have points of instantiation in multiple translation units. If two different points of instantiation give a template specialization different meanings according to the one definition rule (3.2), the program is ill-formed, no diagnostic required.

Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • thanks, still have to translate from standardese into human :) Namely, " the end of the translation unit is also considered a point of instantiation.", don't really understand what it means. Is it saying that you can defer the definition? – vsoftco Aug 07 '14 at 18:13
  • @vsoftco Yes, it means the compiler is allowed to generate the specializations for `void f(my_type1)` and `void f(my_type2)` at the end of the translation unit, instead of their first points of instantiation, which would be within `main()`. – Praetorian Aug 07 '14 at 18:16
  • 2
    I think that last sentence is relevant: "If two different points of instantiation give a template specialization different meanings according to the one definition rule (3.2), the program is ill-formed, no diagnostic required." – Mooing Duck Aug 07 '14 at 18:20
  • @MooingDuck Why? There are no two instantiations of `f` that violate ODR in this case. Note that `f` is not being instantiated at the first POI, and then again at the end of the TU. Only the second is being done (or that's my interpretation). I think that last sentence is mainly for the benefit of specializations that may appear in different TUs anyway. – Praetorian Aug 07 '14 at 18:24
  • 1
    @Praetorian, then why fully-qualified calls cease to work? `::f_helper(typename T::tag_type)`? It's very fishy... – vsoftco Aug 07 '14 at 18:27
  • 1
    @vsoftco Because it's no longer considered dependent in that case, and the name must be visible at the point of definition of `f` (phase 1) rather than point of instantiation (phase 2). – Praetorian Aug 07 '14 at 18:31
  • 3
    Both specializations `f` and `f` have two points of instantiation (PoI) in the code: one immediately after the closing brace of `main` (14.6.4.1/1), and one at the end of the translation unit (14.6.4.1/8). Obviously the two PoI give both specializations of `f` different meanings: the first PoI results in the specializations being ill-formed, and the second does not. – Casey Aug 07 '14 at 20:01
  • @Casey So does that mean the code is ill-formed, or the compilers are correct in rejecting the ill-formed specialization and accepting the other one? – Praetorian Aug 07 '14 at 20:07
  • @Praetorian It means exactly what you quoted in your answer from 14.6.4.1/8: "If two different points of instantiation give a template specialization different meanings according to the one definition rule (3.2) [they do], the program is ill-formed, no diagnostic required." – Casey Aug 08 '14 at 21:44