Reputation: 17628
[ 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).
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.
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
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 Tag
s 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