Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

From Josuttis: Do different template functions, that instantiate to the same function signature given particular types, result in ODR invalidity?

Tags:

c++

templates

In Josuttis' and Vandevoorde's well-known book on templates, C++ Templates: The Complete Guide, they discuss details regarding the overloading of function templates.

In one of their examples, related to a discussion of function signatures and overloaded function templates, they present code that they describe in the following terms:

This program is valid and produces the following output:

(Note: Output shown below)

However, when I build and compile the identical code in Visual Studio 2010, I get a different result. This leads me to believe that either the VS 2010 compiler is producing incorrect code, or that Josuttis is incorrect that the code is valid.

Here is the code. (Josuttis 2003, Section 12.2.1)

// File1.cpp

#include <iostream>

template<typename T1, typename T2>
void f1(T2, T1)
{
    std::cout << "f1(T2, T1)" << std::endl;
}

extern void g();

int main()
{
    f1<char, char>('a', 'b');
    g();
}

...

// File2.cpp

#include <iostream>

template<typename T1, typename T2>
void f1(T1, T2)
{
    std::cout << "f1(T1, T2)" << std::endl;
}

void g()
{
    f1<char, char>('a', 'b');
}

(Notice the reversal of type arguments in the two template function definitions. Note also that this reversal has no effect when the two type arguments are the same, as they are for the two functions f1() in this code example.)

According to Josuttis:

This program is valid and produces the following output:

f1(T2, T1)
f1(T1, T2)

When I build and run the identical code, unchanged, in the Visual Studio 2010 compiler, here is my result:

f1(T1, T2)
f1(T1, T2)

Further, I was wondering how it is possible for the compiler/linker to distinguish between the function f1 as instantiated in file1.cpp, and the function f1 as instantiated in file2.cpp, given that (I think) the compiler strips away all "knowledge" of the fact that these functions were created from templates, and has only the information (I think) of the function signature itself: void (char, char), which is the same for both f1 functions.

Since (if I'm correct) the function signature is identical in the two translation units, I would think that this is an example of a violation of the One Definition Rule (ODR), and that it would therefore be invalid C++.

However, as I've just noted, Josuttis and Vandevoorde claim that this is valid C++.

But since my compiled version of the same code gives different results than Josuttis claims is the output, this seems to be an indication that either VS 2010 is producing incorrect code, or Josuttis is incorrect in this case (i.e., the code is invalid and violates the ODR).

Are Josuttis and Vandevoorde incorrect, or is VS 2010 producing incorrect output? Or is there some other explanation that explains the discrepancy between the output VS 2010 produces, and the output Josuttis reports?

It might be of interest to show the VS 2010 disassembly at the point that each f1() is called.

The first call of f1() (directly within main()):

f1() called directly from within main()

The second call of f1() (from within g()):

f1() called from within g()

Note that the address of f1() chosen by the compiler in both cases is the same - 13E11EAh. To me, this indicates that in fact, the compiler cannot distinguish between the two instantiated function signatures, and this is a case where the ODR is being violated, so the code is invalid C++ and Josuttis has an error in his book. But it's just that - an indication. I don't know.

(I have checked the errata on the book's website, and there is no mention of this example.)

ADDENDUM Per a request from a comment, I am attaching relevant output from the .map file for this program that shows the mangled name/s being used for f1:

.map file output showing mangled names for <code>f1</code>

ADDENDUM 2 Now that the question is answered - Josuttis' book is correct - I want to note that in the Josuttis text, in the same section (12.2.1), it is explicitly outlined exactly what determines a unique function signature, including the template aspect.

From the text (among the other, expected things that define a function signature), the TRANSLATION UNIT is part of the function signature; for template functions (only) the RETURN TYPE is part of the function signature, and

.6. The template parameters and the template arguments, if the function is generated from a function template.

Therefore - it is clear. Template information must be maintained and tracked by the compiler even after a function template has been instantiated, in order for the compiler/linker to obey the necessary, special rules for templates (as in the case of the code example in my question).

like image 216
Dan Nissenbaum Avatar asked Nov 29 '12 15:11

Dan Nissenbaum


1 Answers

Apologies for the earlier, incorrect answer. The example does indeed seem to be correct, and there is actually a similar example in the standard itself (C++11, 14.5.6.1/1-2). Let me just quote it in its entirety:

  1. It is possible to overload function templates so that two different function template specializations have the same type. [ Example:

    // file1.c
    
    template<class T> void f(T*);
    
    void g(int* p) {
        f(p); // calls f<int>(int*)
    }
    
    
    // file2.c
    
    template<class T> void f(T);
    
    void h(int* p) {
        f(p); // calls f<int*>(int*)
    }
    

    end example ]

  2. Such specializations are distinct functions and do not violate the one definition rule (3.2).

In your case, you have two different function templates, both called f1 (which is fine because you can overload function templates), and they happen to have specializations which have the same type.

like image 123
Kerrek SB Avatar answered Nov 15 '22 15:11

Kerrek SB