5
votes

Can I specialize forward declared template? For example:

template <typename T> class A;

template <>
class A<char> {
    char a[1000];
};

int main()
{
    [[maybe_unused]] A<char> a;
    return 0;
}

What do I want to achieve?

As we know, we have to specialize std::hash to use it with some hash-table-based types. Standard std::hash specialization requires to include <functional> in the header file and then specialize it. I use this header file in many places, and the compilation time of <functional> is pretty big. So I want to move my specialization to source (cpp) file.

my_type.hpp:

class my_type {/*...*/};

namespace std {

template <typename T>
struct hash;

template <>
struct hash<my_type>
{
    size_t operator()(my_type m) const;
};
} // namespace std

my_type.cpp:

#include "my_type.hpp"
#include <functional>
namespace std {
size_t std::hash<my_type>::operator()(my_type v) const
{
    return std::hash<decltype(v.value())>{}(v.value());
}
} // namespace std

This solution works, but is it legal in terms of ISO standard?

EDIT/NOTE: It doesn't work with libc++ (clang std implementation), because it defines std::hash as std::__1::hash, where __1 is inline namespace. This partially answers the question.

2

2 Answers

6
votes

The general question about A is that yes, it's allowed. An explicit specialization is disjoint from the primary template. It can be defined as complete or incomplete, regardless of how the primary is defined.

As for your more specific question about std::hash, not it's not alright. You violate

[namespace.std]

1 Unless otherwise specified, the behavior of a C++ program is undefined if it adds declarations or definitions to namespace std or to a namespace within namespace std.

2 Unless explicitly prohibited, a program may add a template specialization for any standard library class template to namespace std provided that (a) the added declaration depends on at least one program-defined type and (b) the specialization meets the standard library requirements for the original template.

Forward declaring std::hash is not a declaration of a specialization that depends on a user defined type. It's a plain declaration that falls under the specification of paragraph 1. There is no wording that allows to forward declare std::hash anywhere else in the standard. So this is undefined behavior.

2
votes

It is not legal because forward-declaring any templates in the std:: namespace is not allowed. For your particular standard library implementation it might work, but std::hash could have other template parameters beyond just the type of the hash, as long as those have default values. For example:

namespace std {

// this is how hash might be declared in the standard library
template <typename T, bool B = some_trait_v<T>>
struct hash;

}

Also see: Forward Declaration of variables/classes in std namespace

However generally, it is permitted to specialize forward-declared templates, just not for templates in the std:: namespace:

template <typename T>
void f();

// this is perfectly normal and allowed
template <>
void f<int>() {
    // do something ...
}

It is unfortunate there there aren't many forward-headers such as <iosfwd>, otherwise this would be a non-issue.