dxiv
dxiv

Reputation: 17628

case of template member function specialization that compiles on msvc, not others

[ EDIT ] I changed the title from works to compiles since it turns out that it doesn't truly work after all (thanks @bogdan for the comments). I added code at the end of the post showing why and how.

The second part of the question still stands - is there a way to "fix" it? The crux of the matter is having a virtual function Observe in a base template<int N> class X be rerouted to a templated function Observe<N> in classes derived from X<N>, without requiring any supporting code in X.

For an example of how it can be done by requiring X to cooperate see this answer to the other question (which basically requires that Observe<N> be declared into the most derived class).


While looking at this other question Choosing which base class to override method of I found out that the following snippet compiles cleanly on vc++ 2015 (with /W4 /Za) and returns the expected output, yet fails to compile on gcc-5.1 and clang 3.7 (tried at ideone.com).

I am aware that specialization of template functions has many pitfalls, but I am still curious which letter of the C++ standard applies in this case, and - in the likely case that the code is not fully compliant - whether there is an easy way to "fix" it.

#include <iostream>
using std::cout;
using std::endl;

typedef int Parameter;

class Observer
{
public:
    virtual void Observe(Parameter p) = 0;
};

class TaggedDispatch
{
public:
    template<size_t Tag> void TObserve(Parameter p);
};

template<size_t Tag>
class TaggedObserver : virtual public TaggedDispatch, public Observer 
{ 
public:
    virtual void Observe(Parameter p) override
    {   TObserve<Tag>(p); }
};

class Thing : public TaggedObserver<0>, TaggedObserver<11>
{   };

template<> void Thing::TObserve<0>(Parameter p)
{   cout << "Parent #  0, Parameter " << p << endl; }

template<> void Thing::TObserve<11>(Parameter p)
{   cout << "Parent # 11, Parameter " << p << endl; }

int main(int, char **)
{
    Thing test;
    test.TObserve<0>(101);
    test.TObserve<11>(999);

    return 0;
}

Output when compiled with vc++ 2015.

Parent #  0, Parameter 101
Parent # 11, Parameter 999

Compile errors fromgcc-5.1

prog.cpp:29:17: error: template-id 'TObserve<0>' for 'void Thing::TObserve(Parameter)' does not match any template declaration
 template<> void Thing::TObserve<0>(Parameter p)
                 ^
prog.cpp:32:17: error: template-id 'TObserve<11>' for 'void Thing::TObserve(Parameter)' does not match any template declaration
 template<> void Thing::TObserve<11>(Parameter p)

Compile errors from clang 3.7.

prog.cpp:22:36: warning: 'override' keyword is a C++11 extension [-Wc++11-extensions]
        virtual void Observe(Parameter p) override
                                          ^
prog.cpp:29:24: error: no function template matches function template specialization 'TObserve'
template<> void Thing::TObserve<0>(Parameter p)
                       ^
prog.cpp:32:1: error: extraneous 'template<>' in declaration of variable 'TObserve'
template<> void Thing::TObserve<11>(Parameter p)
^~~~~~~~~~
prog.cpp:32:24: error: variable has incomplete type 'void'
template<> void Thing::TObserve<11>(Parameter p)
                       ^
prog.cpp:32:32: error: expected ';' at end of declaration
template<> void Thing::TObserve<11>(Parameter p)
                               ^
                               ;
prog.cpp:32:32: error: expected unqualified-id
1 warning and 5 errors generated.


[ EDIT ] It doesn't truly work in vc++ 2015 after all. What appears to happen is that the compiler allows the void Thing::TObserve<0> definition, but internally maps it to void TaggedDispatch::TObserve<0>. This becomes obvious if adding another derived class e.g.

class Other : public TaggedObserver<0>
{    };

template<> void Other::TObserve<0>(Parameter p)
{    cout << "Parent # 00, Parameter " << p << endl; }

Then the vc++ 2015 compile fails with the error message:

error C2766: explicit specialization; 'void TaggedDispatch::TObserve<0>(Parameter)' has already been defined

Upvotes: 1

Views: 463

Answers (1)

bogdan
bogdan

Reputation: 9317

MSVC is wrong to accept the code; Clang and GCC (and EDG) are right to reject it.

This case is similar to the one in this question, but it involves a different syntactic construct (and different code paths in the compilers, yielding different Standard conformance results, with only EDG being consistent).

In template<> void Thing::TObserve<0>(Parameter p), Thing::TObserve<0> is a declarator-id, with Thing:: being a nested-name-specifier. [8.3p1] says:

[...] When the declarator-id is qualified, the declaration shall refer to a previously declared member of the class or namespace to which the qualifier refers (or, in the case of a namespace, of an element of the inline namespace set of that namespace (7.3.1)) or to a specialization thereof; the member shall not merely have been introduced by a using-declaration in the scope of the class or namespace nominated by the nested-name-specifier of the declarator-id. [...]

So, you have to use template<> void TaggedDispatch::TObserve<0>. As noted in the question, using Thing:: could create the false impression that you can provide different explicit specializations of TObserve for different derived classes, which is not the case. There's only one TObserve member function template, the one declared in TaggedDispatch, and all such explicit specializations (and implicit or explicit instantiations, and partial specializations) are "attached" to that declaration.


One solution to make things work the way you expect is to declare an Observe member function template in each derived Thing-like class, possibly providing explicit specializations for relevant Tags if necessary, and let specializations of the template be automatically wired up to the corresponding Observer interface instance using CRTP:

#include <iostream>
#include <cstddef>

using Parameter = int;

struct Observer 
{
   virtual void Observe(Parameter p) = 0;
};

template<std::size_t Tag> struct TaggedObserver : Observer { };

template<class Derived, std::size_t Tag> struct CrtpObserver : TaggedObserver<Tag>
{
   void Observe(Parameter p) override
   {
      static_cast<Derived*>(this)->template Observe<Tag>(p);
   }
};

struct Thing : CrtpObserver<Thing, 0>, CrtpObserver<Thing, 1>
{
   template<std::size_t N> void Observe(Parameter p);
};

template<> void Thing::Observe<0>(Parameter p)
{
   std::cout << "Interface #0, Parameter " << p << '\n';
}

template<> void Thing::Observe<1>(Parameter p)
{
   std::cout << "Interface #1, Parameter " << p << '\n';
}

int main()
{
   Thing test;
   TaggedObserver<0>* p0 = &test;
   TaggedObserver<1>* p1 = &test;
   p0->Observe(7);
   p1->Observe(3);
}

This places the implementations of the Observer interface in Thing where they belong, while requiring minimal plumbing in each derived class - not much more than what you would have to do anyway if you could separately override each Observer::Observe directly.

Upvotes: 1

Related Questions