Specialization of template after instantiation?

2019-06-17 08:12发布

问题:

My full code is too long, but here is a snippet that will reflect the essence of my problem:

class BPCFGParser {
  public:

  ...
  ...

  class Edge {
    ...
    ...
  };


  class ActiveEquivClass {
    ...
    ...
  };

  class PassiveEquivClass {
    ...
    ...
  };

  struct EqActiveEquivClass {
    ...
    ...
  };

  struct EqPassiveEquivClass {
    ...
    ...
  };



  unordered_map<ActiveEquivClass, Edge *, hash<ActiveEquivClass>, EqActiveEquivClass> discovered_active_edges;
  unordered_map<PassiveEquivClass, Edge *, hash<PassiveEquivClass>, EqPassiveEquivClass> discovered_passive_edges;

};

namespace std {


template <>
class hash<BPCFGParser::ActiveEquivClass>
{

    public:
        size_t operator()(const BPCFGParser::ActiveEquivClass & aec) const {

        }
};

template <>
class hash<BPCFGParser::PassiveEquivClass>
{

    public:
        size_t operator()(const BPCFGParser::PassiveEquivClass & pec) const {

        }
};

}

When I compile this code, I get the following errors:

In file included from BPCFGParser.cpp:3,
                 from experiments.cpp:2:
BPCFGParser.h:408: error: specialization of ‘std::hash<BPCFGParser::ActiveEquivClass>’     after instantiation
BPCFGParser.h:408: error: redefinition of ‘class                 std::hash<BPCFGParser::ActiveEquivClass>’
/usr/include/c++/4.3/tr1_impl/functional_hash.h:44: error: previous definition of     ‘class std::hash<BPCFGParser::ActiveEquivClass>’
BPCFGParser.h:445: error: specialization of     ‘std::hash<BPCFGParser::PassiveEquivClass>’ after instantiation
BPCFGParser.h:445: error: redefinition of ‘class std::hash<BPCFGParser::PassiveEquivClass>’
/usr/include/c++/4.3/tr1_impl/functional_hash.h:44: error: previous definition of     ‘class std::hash<BPCFGParser::PassiveEquivClass>’

Now I have to specialize std::hash for these classes (because standard std::hash definition does not include user defined types). When I move these template specializations before the definition of class BPCFGParser, I get a variety of errors for a variety of different things tried, and somewhere (http://www.parashift.com/c++-faq-lite/misc-technical-issues.html) I read that:

Whenever you use a class as a template parameter, the declaration of that class must be complete and not simply forward declared.

So I'm stuck. I cannot specialize the templates after BPCFGParser definition, I cannot specialize them before BPCFGParser definition, how may I get this working?


You need to move the specialization into an inner class inside of BPCFGParser. Doing so meets both requirements.

Thank you very much for the answer :)

hash class is defined within the namespace std. It does not allow me to specialize the templates for hash in a non-namespace scope. Even the following:

template <>
  class std::hash<ActiveEquivClass> {
...

did not work. When I enclose the specializations with namespace std {}, however, it gives the weird error of:

In file included from BPCFGParser.cpp:3,
                 from experiments.cpp:2:
BPCFGParser.h:225: error: expected unqualified-id before ‘namespace’
experiments.cpp:7: error: expected `}' at end of input
BPCFGParser.h:222: error: expected unqualified-id at end of input

In an answer given in velocityreviews, someone claims that namespaces cannot be defined within classes. So I'm still stuck.

回答1:

You need to move the specialization into an inner class inside of BPCFGParser. Doing so meets both requirements

  1. Specialization is after the complete definition of ActiveEquivClass
  2. Before the use of the specialization

Example:

class BPCFGParser {

  class ActiveEquivClass {
    ...
  };

  template <>
  class hash<ActiveEquivClass> {
     public:
        size_t operator()(const BPCFGParser::ActiveEquivClass & aec) const {
        }
  };
  ...
  unordered_map<ActiveEquivClass, Edge *, hash<ActiveEquivClass>, EqActiveEquivClass> discovered_active_edges;

};


回答2:

Try moving the hash<> template specialization code before the BPCFGParser class declaration. The error means that hash is expanded based on std::hash defined at /usr/include/c++/4.3/tr1_impl/functional_hash.h; So your specialization is not used before the instantiation. Ideally, your specialization code should be made available for the compiler before the template is expanded.



回答3:

Whenever you use a class as a template parameter, the declaration of that class must be complete and not simply forward declared.

This is not actually true. The limitations of when template parameters have to be complete types is generally dependent on the template contents; but it is not illegal to instantiate a template with an incomplete type as long as the template does not contain code that is illegal with an incomplete type.

The only way to approach your problem is to define the specialization before the class, but to define the actual member function operator() after the class. That is,

template <>
class std::hash<BPCFGParser::ActiveEquivClass>
{
public:
    size_t operator()(const BPCFGParser::ActiveEquivClass & aec) const;
};

// definition of BPCFGParser

template<> std::size_t std::hash<BPCFGParser::ActiveEquivClass>::operator()(const BPCFGParser::ActiveEquivClass & aec) const {
}

This also means, no nested classes, as you cannot forward declare a nested class.



回答4:

I've exactly the same problem and finally come up with a workaround user-defined hash functor solution, see below:

class Outer
{
    // TC++PL, 4e, 19.4.1 : A friend must be previously declared in an enclosing scope or
    // defined in the non-class scope immediately enclosing the class that is declaring it to be a friend. 
    struct Hash_inner;

    class Inner
    {
        int i;
        friend struct Hash_inner;
    };

    struct Hash_inner
    {
        size_t operator()(const Inner& in) const
        { return std::hash<int>()(in.i); }
    };

    std::unordered_map<Inner, int, Hash_inner> um;
};

And I'm stil wondering whether there's a std::hash specialization approach. Can anyone figure it out?



回答5:

I know this question is quite old, but I've just come across the same problem and I thought I'd report my findings.

The error message:

In file included from BPCFGParser.cpp:3,
             from experiments.cpp:2:
BPCFGParser.h:408: error: specialization of ‘std::hash<BPCFGParser::ActiveEquivClass>’     after instantiation
BPCFGParser.h:408: error: redefinition of ‘class                 std::hash<BPCFGParser::ActiveEquivClass>’
/usr/include/c++/4.3/tr1_impl/functional_hash.h:44: error: previous definition of     ‘class std::hash<BPCFGParser::ActiveEquivClass>’
BPCFGParser.h:445: error: specialization of     ‘std::hash<BPCFGParser::PassiveEquivClass>’ after instantiation
BPCFGParser.h:445: error: redefinition of ‘class std::hash<BPCFGParser::PassiveEquivClass>’
/usr/include/c++/4.3/tr1_impl/functional_hash.h:44: error: previous definition of     ‘class std::hash<BPCFGParser::PassiveEquivClass>’

isn't complaining about your classes at all, it's saying that you can't specialize std::hash because either the generic template for std::hash or one of the existing specializations has already been used - i.e. someone has used it between the point that it was defined and the point where you're attempting to specialize it.

There is some text describing this in the "in detail" section of this document:

Specialization must be declared before the first use that would cause implicit instantiation

In my case, the problem wasn't with my specialization code, the problem was with its position. Once std::hash has been used at all, you can't specialize it any further.

I found that if I moved my specialization code to immediately after including <unordered_map>, it worked fine.

The suggestion by Puppy to separate the declaration of the specialization from the implementation allows you to move the declarations very close to the inclusion of <unordered_map>, the implementation can come later wherever it's convenient (after BPCFGParser is fully defined in your case).