简体   繁体   中英

Specialization of class for vectors of specific type_trait

I'm trying to specialize hash to include std::vector for all arithmetic types, but it's throwing a few errors

./includes/helpers.hpp:14:22: error: default template argument in a class template partial specialization
      typename = std::enable_if_t<std::is_arithmetic<dtype>::value> >
                 ^
./includes/helpers.hpp:16:8: error: class template partial specialization contains a template parameter that cannot be deduced; this partial specialization will never be used [-Wunusable-partial-specialization]
struct hash<std::vector<dtype> >
       ^~~~~~~~~~~~~~~~~~~~~~~~~

I tried to follow as close as I could with different enable_if_t guides. But it doesn't seem to be working, what am I doing wrong?

It seems to work without using enable_if_t. But then there would be a possible conflict with vectors that shouldn't use this hash

This is my code so far (editted to be more "complete")

#include <iostream>
#include <type_traits>
#include <vector>

namespace std {

    template <typename dtype,
              typename = std::enable_if_t< std::is_arithmetic<dtype>::value> >

    struct hash<std::vector<dtype> > {
        size_t operator()(const std::vector<dtype> &input)
        {
            //perform hash
        }
    };
}

using namespace std;

int main()
{
    const vector<int> i{1,2,3,4};
    cout << hash<vector<int>>()(i) << endl;

    return 0;
}

The problem is, that std::hash has only a single template parameter and you cannot add an additional defaulted template parameter in a partial specialization. So you have several choices, depending on what you want to do with your hash.

Please also rethink your approach. This comment by Yakk is very useful:

You may not specialize templates in std unless the specialization depends on a user-provided type. – Yakk

You can easily fix thisby not putting your own hash into the std namespace.

  1. You could simply remove the enable_if from the template argument list and replace it with a

     static_assert(std::is_arithmetic<dtype>::value, "!"); 

    in the struct body, given that you only ever want to hash vectors of arithmetic type.

  2. SFINAE on the call operator. This way, you have to provide all the hash methods for all other vector types within the same struct. Also you have to go through some funny business of repeating the template parameter to make the compiler happy. It's very important that your SFINAE criteria are mutually exclusive, otherwise you'll get horrible errors.

     #include <iostream> #include <string> #include <type_traits> #include <vector> namespace std { template< typename dtype > struct hash< std::vector<dtype> > { template< typename T = dtype > std::enable_if_t<std::is_arithmetic<T>::value, size_t> operator()(const std::vector<T> &input) const { constexpr size_t FNV_prime = 1099511628211ul; constexpr size_t FNV_offset = 14695981039346656037ul; size_t hashed = FNV_offset; for(const auto &n:input) { hashed ^= n; hashed *= FNV_prime; } return hashed; } template< typename T = dtype > std::enable_if_t<!std::is_arithmetic<T>::value, size_t> operator()(const std::vector<T> &input) const { std::cout << "No hash for you :-(\\n"; return 0; } }; } // namespace std int main() { { std::vector<int> v{1,2,3,4}; size_t hash = std::hash<std::vector<int>>{}(v); std::cout << hash << "\\n"; } { std::vector<std::string> v{"Hello", "world!"}; size_t hash = std::hash<std::vector<std::string>>{}(v); std::cout << hash << "\\n"; } } 

    Live example

  3. You can also declare your own struct for hashing and add as many template parameters as you want. Then you just need std::hash to inherit from your custom struct.

     #include <iostream> #include <string> #include <type_traits> #include <vector> template < typename T, bool = std::is_arithmetic<T>::value > struct vector_hash; template < typename T> struct vector_hash<T,true> { size_t operator()(std::vector<T> const &input) const { constexpr size_t FNV_prime = 1099511628211ul; constexpr size_t FNV_offset = 14695981039346656037ul; size_t hashed = FNV_offset; for(const auto &n:input) { hashed ^= n; hashed *= FNV_prime; } return hashed; } }; template < typename T> struct vector_hash<T,false> { size_t operator()(std::vector<T> const &) const { std::cout << "No hash for you :-(\\n"; return 0; } }; namespace std { template< typename dtype > struct hash< std::vector<dtype> > : vector_hash<dtype> {}; } // namespace std int main() { { std::vector<int> v{1,2,3,4}; size_t hash = std::hash<std::vector<int>>{}(v); std::cout << hash << "\\n"; } { std::vector<std::string> v{"Hello", "world!"}; size_t hash = std::hash<std::vector<std::string>>{}(v); std::cout << hash << "\\n"; } } 

    Live example

It is illegal to specialize a template in namespace std, unless you do it on a user provuded type. Vector is not user provided.

What you need to do is:

namespace helper{
  template<class T, class=void>
  struct hash:std::hash<T>{};
}

now you can do usual sfinae tricks with helper's hash, or extend it willy-nilly.

namespace helper {
  template <typename dtype>
  struct hash<std::vector<dtype>,
     std::enable_if_t< std::is_arithmetic<dtype>::value> 
  > {
    size_t operator()(const std::vector<dtype> &input) const {
        //perform hash
    }
  };
}

Simply pass helper::hash<T> in place of std hash. The extra void defaulted parameter permits sfina specialization, the base spec forwards std hash, and there is no ill-formedness issues.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM