9

Here's what I mean:

// test.h
class cls
{
public:
    template< typename T >
    void f( T t );
};

-

// test.cpp
template<>
void cls::f( const char* )
{
}

-

// main.cpp
int main()
{
    cls c;

    double x = .0;
    c.f( x ); // gives EXPECTED undefined reference (linker error)

    const char* asd = "ads";
    c.f( asd ); // works as expected, NO errors

    return 0;
}

This is completely fine, right?

I started doubting this, because I just ran over the specialization of '...' after instantiation error, which was new to me. So, I "worked around" this error and everything seems to work fine now, but still..

Is this well-defined behavior?


edit: And the same for non-member template functions (forward declared non-member template functions).

Kiril Kirov
  • 37,467
  • 22
  • 115
  • 187

3 Answers3

6

Lightness Races in Orbit cited why it's not compliant parts from the Standard. There might be some others, in the vicinity.

I will try to explain in simpler terms what the Standard verbiage means, and hopefully I'll get it correctly, and finally explain the linker errors (or absence of error):

  1. What is the point of instantiation ?
  2. How does the compiler select a specialization ?
  3. What is necessary at the point of instantiation ?
  4. Why a linker error ?

1/ What is the point of instantiation ?

The point of instantiation of a template function is the point where it is called or referred to (&std::sort<Iterator>) with all the template parameters fleshed out (*).

template <typename T>
void foo(T) { std::cout << typeid(T).name() << "\n"; }

int main() { foo(1); } // point of instantiation of "foo<int>(int)"

It can be delayed though, and thus not match the exact call site, for templates called from other templates:

template <typename T>
void foo(T) { std::cout << typeid(T).name() << "\n"; }

template <typename T>
void bar(T t) { foo(t); } // not a point of instantiation, T is still "abstract"

int main() { foo(1); } // point of instantiation of "bar<int>(int)"
                       // and ALSO of "foo<int>(int)"

This delay is very important as it enables writing:

  • co-recursive templates (ie, templates that refer to each-others)
  • user-specializations

(*) Roughly speaking, there are exceptions such as non-template methods of a template class...


2/ How does the compiler select a specialization ?

At the point of instantiation, a compiler need to be able to:

  • decide which base template function to call
  • and possibly, which of its specializations to call

This old GotW shows off the woes of specializations... but in short:

template <typename T> void foo(T);   // 1
template <typename T> void foo(T*);  // 2

are overloads, and each spawns a distinct family of possible specializations of which they are the base.

template <> void foo<int>(int);

is a specialization of 1, and

template <> void foo<int*>(int*);

is a specialization of 2.

In order to resolve the function call, the compiler will first pick the best overload, while ignoring template specializations, and then, if it picked a template function, check if it has any specialization that could better apply.


3/ What is necessary at the point of instantiation ?

So, from the way a compiler resolve the call, we understand why the Standard specifies that any specialization should be declared before its first point of instantiation. Otherwise, it simply would not be considered.

Thus, at the point of instantiation, one needs to have already seen:

  • a declaration of the base template function to be used
  • a declaration of the specialization to be selected, if any

But what of the definition ?

It is not needed. The compiler assumes it will either be provided later on in the TU or by another TU entirely.

Note: it does burden the compiler because it means it needs to remember all the implicit instantiations it encountered and for which it could not emit a function-body so that when it finally encounters the definition it can (at last) emit all the necessary code fo all the specializations it encountered. I wonder why this particular approach was selected, and also wonder why even in the absence of an extern declaration the TU may end with undefined function-bodies.


4/ Why a linker error ?

Since no definition is provided, gcc trusts you to provide it later and simply emits a call to an unresolved symbol. If you happen to link with another TU that provides this symbol, then everything will be fine, and otherwise you'll get a linker error.

Since gcc follows the Itanium ABI we can simply look up how it mangles the symbols. It turns out that the ABI makes no difference in mangling specializations and implicit instantiations thus

cls.f( asd );

calls _ZN3cls1fIPKcEEvT_ (which demangles as void cls::f<char const*>(char const*)) and the specialization:

template<>
void cls::f( const char* )
{
}

also produces _ZN3cls1fIPKcEEvT_.

Note: it is not clear to me whether an explicit specialization could have been given a different mangling.

Community
  • 1
  • 1
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
5

No, I don't think it's okay:

[C++11: 14/6]: A function template, member function of a class template, or static data member of a class template shall be defined in every translation unit in which it is implicitly instantiated (14.7.1) unless the corresponding specialization is explicitly instantiated (14.7.2) in some translation unit; no diagnostic is required.

[C++11: 14.7.3/6]: If a template, a member template or a member of a class template is explicitly specialized then that specialization shall be declared before the first use of that specialization that would cause an implicit instantiation to take place, in every translation unit in which such a use occurs; no diagnostic is required. [..]

Frankly I can't explain why it works for you.

Community
  • 1
  • 1
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • *"Frankly I can't explain why it works for you."* Well there's some symbol `void cls::f( const char* )` with external linkage in the other TU. So the symbol *can* be found. IIRC you can use declared, but undefined function templates (just like normal functions); you could provide the definition after the call in the same TU. – dyp Apr 10 '14 at 13:46
  • What is `template<> void cls::f( const char* ) {}` if not an explicit instantiation? – R. Martinho Fernandes Apr 10 '14 at 13:48
  • @R.MartinhoFernandes: Ah, so explicit specialisations work as explicit instantiations too? – Lightness Races in Orbit Apr 10 '14 at 13:49
  • I think so: see 14.7, paragraph 4. But the wording seems a bit confusing. – R. Martinho Fernandes Apr 10 '14 at 13:50
  • @R.MartinhoFernandes: I think I read that passage differently. – Lightness Races in Orbit Apr 10 '14 at 13:53
  • 1
    No, wait. 14.7.2. defines "explicit instantiation" clearly. Argh, I hate this style of description full of forward-defined stuff :( – R. Martinho Fernandes Apr 10 '14 at 13:53
  • Hm, I'm more confused than the beginning. But what I think is the same as what @dyp wrote. And as there's no default body for this function, there's no chance to execute the wrong function. I mean - of there _was_ a "default" implementation _and_ specialization somewhere _else_ - then yes, it could be ambiguous and could lead to some unexpected/random results. But in this case, such "mistake" cannot be done. So, finally? – Kiril Kirov Apr 10 '14 at 13:57
  • Btw, did you consider what 14.7.1 says about things only being "implicitly instantiated" if they are used in a context that requires a completely-defined object type/a definition? I think there is no implicit instantiation in this example: you can use functions without their definition. It works because the compiler just emits the normal "call undefined symbol" object code and then the linker finds that symbol in the other TU. Id est, it doesn't behave much different from normal function calls when only a declaration is present. – R. Martinho Fernandes Apr 10 '14 at 13:58
  • @KirilKirov Note that I don't say it's safe (well-defined behaviour), I'm just guessing *why* its working in this specific case+implementation. – dyp Apr 10 '14 at 14:02
  • @dyp - absolutely. I noted that, I just mentioned, that my explanation is the same. – Kiril Kirov Apr 10 '14 at 14:03
  • @R.MartinhoFernandes Doesn't the "context that requires a function definition to exist" refer to the ODR? It doesn't say that definition must exist in the same TU. – dyp Apr 10 '14 at 14:03
  • Oh, good point. Dammit, the standard seems impenetrable to me today. I'll just shut up. – R. Martinho Fernandes Apr 10 '14 at 14:06
  • That leads to the question: What *is* the implicit instantiation of a function template? (Related: [CWG active issue 212](http://www.open-std.org/JTC1/SC22/WG21/docs/cwg_active.html#212)) Does it instantiate only the declaration? The definition? Does that depend on something? – dyp Apr 10 '14 at 14:12
  • OP's code appears to satisfy the parts of the standard which you have quoted. – Oktalist Apr 10 '14 at 14:54
  • 1
    @Oktalist How does it satisfy the second requirement (about the declaration of explicit specialization before the first use)? How can the code in `main.cpp` be aware of that the explicit specialization of the primary template is defined in other translation unit? – Constructor Apr 10 '14 at 15:05
  • 2
    @Constructor whoops, I imagined a part of the OP's code which wasn't really there (a _declaration_ of the explicit specialization in the header file). That's what's missing, of course. – Oktalist Apr 10 '14 at 15:54
  • 14.7.1/9 looks interesting. – Alsk Apr 10 '14 at 17:43
2

I think that your original code was incorrect and your "workaround" is not standard-compliant, too (despite the fact that your compiler and linker process it). Good quotes from the standard were cited in the answer of @Lightness Races in Orbit. See also the following example from the standard ([temp.expl.spec] 14.7.3/6):

class String { };
template<class T> class Array { /* ... */ };
template<class T> void sort(Array<T>& v) { /* ... */ }

void f(Array<String>& v) {
  sort(v);          // use primary template
                    // sort(Array<T>&), T is String
}

template<> void sort<String>(Array<String>& v); // error: specialization
                                                // after use of primary template
template<> void sort<>(Array<char*>& v);        // OK: sort<char*> not yet used

I marked my answer as community wiki because in fact it is only a big comment.

Community
  • 1
  • 1
Constructor
  • 7,273
  • 2
  • 24
  • 66