Ryan V. Bissell Ryan V. Bissell - 11 days ago 3
C++ Question

Declaration doesn't solve 'explicit specialization after instantiation' error

Suppose I am attempting to create my own implementation of boost::filesystem::path, using the Curiously Recurring Template Pattern:

(Code is given incomplete for brevity, but will exhibit the problem as stated when compiled with '

g++ -std=c++11 -o mypath ./mypath.cpp
', using GCC 4.8.4)

mypath.hpp:

#ifndef MYPATH_HPP
#define MYPATH_HPP

#include <string>
#include <vector>

namespace my {

template <class T>
class PathBase
{
public:
PathBase();
PathBase(std::string const& p);

std::string String() const;

bool IsSeparator(char c) const;
std::string Separators() const;

typedef std::vector<std::string> pathvec;

protected:
pathvec _path;

private:
virtual std::string _separators() const =0;
};


class Path : public PathBase<Path>
{
public:
Path();
Path(std::string const& p);

private:
virtual std::string _separators() const final;
};

} // namespace 'my'

#endif // MYPATH_HPP


mypath.cpp:

#include "mypath.hpp"

namespace my {

//////////template class PathBase<Path>;

template<>
bool PathBase<Path>::IsSeparator(char c) const
{
return (Separators().find(c) != std::string::npos);
}

template <>
std::string PathBase<Path>::Separators() const
{
return _separators();
}

} // namespace

int main(int argc, char** argv)
{
return 0;
}


Of course I discovered that the code as-written will not compile, since I explicitly specialize
Separators()
after
IsSeparator()
has implicitly instantiated it. But I don't particularly want to play whack-a-mole trying to keep all my methods favorably ordered.

While researching similar questions on SO, I found that this accepted answer to one of them suggested that I could solve this problem neatly by merely declaring my specialization. But...


  1. My commented-out
    template class PathBase<Path>;
    line in mypath.cpp had no effect on the problem, and

  2. It kinda feels like my header file already declares the explicit specialization with its entire
    class Path : public PathBase<Path> { ... }
    declaration.



Exactly what does my explicit declaration need to look like?

Answer

Let's get these out of the way first:

  1. template class PathBase<Path>; does not declare an explicit specialization; it is an explicit instantiation definition. You're requesting that the compiler instantiate PathBase<Path> and all its members for which it has definitions, based on the definitions you provided up to that point. In this specific case, it doesn't make any difference indeed.

    The declaration of an explicit specialization would look like template<> class PathBase<Path>;, but that's not what you want here either; see below.

  2. The use of PathBase<Path> when defining Path doesn't declare an explicit specialization either; it triggers an implicit instantiation of PathBase<Path>, based on the definition you provided above. An implicit instantiation for a class template instantiates the class definition and only the declarations of its member functions; it doesn't try to instantiate the definitions of the functions; those are only instantiated when needed, later on.


In your cpp file, you're explicitly specializing IsSeparator and Separators for an implicitly instantiated PathBase<Path>. You're requesting that the compiler instantiate PathBase<Path> based on the generic definition you provided, but, when the definitions of those particular functions are needed, use the specific definitions you provide.

It's basically a shorthand alternative to explicitly specializing the whole class template, when the structure of the class and most of the generic definitions for the members are fine, and you only want to fine-tune the definitions of a few members. If you explicitly specialized the whole class template, you'd have to provide a separate class definition and definitions for all the member functions of the specialization, which would mean unnecessary copy-paste.

You need to tell the compiler about those explicit specializations as soon as possible, before there's any chance that some code would attempt to use the definitions (it needs to know that it will have to look for specific definitions instead of generic ones). You do that by declaring (not necessarily defining) the explicit specializations.

The safest place to do that is immediately after the closing brace of the definition of template <class T> class PathBase. Something like:

class Path;
template<> std::string PathBase<Path>::Separators() const;
template<> bool PathBase<Path>::IsSeparator(char c) const;

You definitely need to do this in the header file, not in a cpp file, otherwise other cpp files that use the header will not know about the explicit specializations and will try to instantiate generic versions (if they need them). That will make your program ill-formed, no diagnostic required (this applies to your example as well). What that means is: if the compiler is smart enough to diagnose the problem, you should be grateful; if it isn't, you can't complain, and it's still your fault.

Having declared the explicit specializations up front, the definitions can come later, possibly in a separate cpp file; that's fine, just like for normal functions.

Also note that, should you want to include the definitions for the explicit specializations in the header file (to ease inlining, for example), you'll have to declare them inline, again like for normal functions. Otherwise, including the header in multiple cpp files will make the program ill-formed, NDR (you'll typically get multiple definition errors at link time).


Obligatory standard quote from [temp.expl.spec]/7:

[...] When writing a specialization, be careful about its location; or to make it compile will be such a trial as to kindle its self-immolation.

Yes, the members of the standardization committee are human too.