Specialization of Member Function Template After Instantiation Error, and Order of Member Functions

Specialization of member function template after instantiation error, and order of member functions

Your first code is not correct by standard.

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

In your case - implicit instantiation of bar function with type bool is required by its usage in foo<bool>, before explicit specialization declaration.

something confusing about class template partial specialization & class member specialization

The explicit specialization of Foo<double, int>::bar

template<>
template<>
void Foo<double, int>::bar(double) {
std::cout << "Now I'm specialized!\n";
}

causes an implicit instantiation of Foo<double, int>. This is a better match than the partially specialized Foo<T, int>, so you get Foo_0 instead of Foo_1, unless you comment out the specialization of bar.

What you could do is to move bar(double) into the general class template Foo<T, S> as a regular overloaded member function

template<class T, class S>
class Foo {
// as before

void bar(double) {
std::cout << "Now I'm overloaded!\n";
}
};

Now you will get Foo_1, live example. Note that you won't be able to call Foo<double, int>::bar(double) anymore. If you want that, then you need to add a bar member to the partial specialization Foo<T, int> as well.

Is it safe to place definition of specialization of template member function (withOUT default body) in source file?

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.

Constrained member functions and explicit template instantiation

This is a bug of the c++20 experimental implementation of both GCC and Clang.

This is reported as GCC bug #77595.

In the c++20 paragraph [temp.explicit]/11:

An explicit instantiation that names a class template specialization is also an explicit instantiation of the same kind (declaration or definition) of each of its members (not including members inherited from base classes and members that are templates) that has not been previously explicitly specialized in the translation unit containing the explicit instantiation, provided that the associated constraints, if any, of that member are satisfied by the template arguments of the explicit instantiation ([temp.constr.decl], [temp.constr.constr]), except as described below. [...]

According to this c++20 adendum "provided that the associated constraints, if any, of that member are satisfied" means that only one of the two Dot overload shall be explicitly instantiated.

The bolded clause "except as described below" was there in the c++17 standard. It does apply to the first clause "An explicit instantiation that names a class template specialization is also an explicit instantiation of the same kind (declaration or definition) of each of its members". The interpretation of this exception has not changed with c++20. Probably that the commitee overlooked that, grammaticaly, it could be correct to apply the exception to the c++20 adendum.

Below is the c++17 version of this paragraph:

An explicit instantiation that names a class template specialization is also an explicit instantiation of the same kind (declaration or definition) of each of its members (not including members inherited from base classes and members that are templates) that has not been previously explicitly specialized in the translation unit containing the explicit instantiation, except as described below.

In this shorter sentence the meaning is clear.

Member function instantiation

The member functions of a class template are instantiated only when required by a context, which means you will not see any error until you try to use new_t(). The related section from the C++ standard is:

§ 14.7.1 Implicit instantiation [temp.inst]



  1. Unless a function template specialization has been explicitly instantiated or explicitly specialized, the function template specialization is implicitly instantiated when the specialization is referenced in a context that requires a function definition to exist. Unless a call is to a function template explicit specialization or to a member function of an explicitly specialized class template, a default argument for a function template or a member function of a class template is implicitly instantiated when the function is called in a context that requires the value of the default argument.

  2. [ Example:

    template<class T> struct Z {
    void f();
    void g();
    };

    void h() {
    Z<int> a; // instantiation of class Z<int> required
    Z<char>* p; // instantiation of class Z<char> not required
    Z<double>* q; // instantiation of class Z<double> not required
    a.f(); // instantiation of Z<int>::f() required
    p->g(); // instantiation of class Z<char> required, and
    // instantiation of Z<char>::g() required
    }

    Nothing in this example requires class Z<double>, Z<int>::g(), or Z<char>::f() to be implicitly
    instantiated. — end example ]

When are member functions of a templated class instantiated?

When are member functions of a templated class instantiated?

The declaration of a class template specialization's member function is instantiated along with the class specialization, but the definition is only instantiated when needed. Usually when the member function is called. So long as nothing uses the member function, the definition may go un-instantiated.

Your example calls the member function, so the definition must be instantiated.

There may however be multiple points of instantiation for a member function of a specialization. One is immediately before being used, but an additional one (that is always added) is at the end of the translation unit

[temp.point] (emphasis mine)

8 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, the program is ill-formed, no
diagnostic required.

Which brings me to the next point,fooBase has two points of instantiations in the translation unit you show. In one ABC is incomplete, while in the other it has been completed. Under the paragraph above, your program is ill-formed, no diagnostic required. A compiler can be silent about the violation, all the while emitting some code the seems to work. But that does not make the program valid. If the standard is updated in the future to require a diagnostic in this case, the illegal code will fail to build. It may even fail now if compilers wish to diagnose it.

Partial specialization of member functions

I found a solution!

Since it seems that member functions cannot be partially specialized, I solved the problem by creating a new class that does not use the IndexType parameter.

class Evaluator
{
public:
template <class DataType> DataType fromString(const string &val);
};

In the .cpp file, I added:

// Templated default - needs to be overriden
template<class DataType> DataType
Evaluator::fromString(const string &val) {
Assert(false, "Specialize me!");
return DataType();
}

// Specializations.
// NOTE: All template specializations must be declared in the namespace scope to be
// C++ compliant. Shame on Visual Studio!
template<> string
Evaluator::fromString(const string &val) { return val; }

template<> S32
Evaluator::fromString(const string &val) { return atoi(val.c_str()); }

template<> U32
Evaluator::fromString(const string &val) { return atoi(val.c_str()); }

template<> U16
Evaluator::fromString(const string &val) { return atoi(val.c_str()); }

template<> DisplayMode
Evaluator::fromString(const string &val) { return stringToDisplayMode(val); }

template<> YesNo
Evaluator::fromString(const string &val) { return stringToYesNo(val); }

template<> RelAbs
Evaluator::fromString(const string &val) { return stringToRelAbs(val); }

template<> ColorEntryMode
Evaluator::fromString(const string &val) { return stringToColorEntryMode(val); }

template<> GoalZoneFlashStyle
Evaluator::fromString(const string &val) { return stringToGoalZoneFlashStyle(val); }

template<> Color
Evaluator::fromString(const string &val) { return Color::iniValToColor(val); }

Then, in the Setting class, I replaced the similar block of code with this call:

DataType fromString(const string &val) { 
return mEvaluator.fromString<DataType>(val);
}

This seems to work nicely, and is pretty readable.

If anyone is interested, the full code will be available at the link below, after it is more fully tested and checked in.

https://code.google.com/p/bitfighter/source/browse/zap/Settings.h



Related Topics



Leave a reply



Submit