使用正确的容器类型,具体取决于可以使用的模板

时间:2018-02-08 23:10:11

标签: c++ templates template-specialization

我正在开发一个需要关联容器的C ++类(mapunordered_map

我的班级看起来像这样:

    template<typename T>
    class myClass
    {
            static_assert(
                std::is_copy_constructible<T>::value,
                "content of myClass should be copy constructible"
            );
            // TODO: check if T::operator== exists

            /* if hash<T> is available */
            using container = std::unordered_map<T,T>;
            /* if hash<T> is unavailable */
            using container = std::map<T,T>;

        public:
            myClass() = default;

            [...]

        private:
            container m_mapping;
    }

我希望从模板T自动推断出容器:

  • 如果std::hash<T>可用,我们应该使用unordered_map来提高性能。
  • 如果std::hash<T>不可用,我们应该回到map

有没有办法使用c ++模板?

5 个答案:

答案 0 :(得分:6)

这可以通过一个相当简单的帮助模板来完成:

class A {
  method() {
    throw 'Error';
  }

  get try() {
    return new Proxy(this, {
      // Intercept method getter
      get(target, name) {
        if (typeof target[name] === 'function') {
          return new Proxy(target[name], {
            // Intercept method call
            apply(target, self, args) {
              try {
                return target.apply(self, args);
              } catch(e) {
                // swallow error
              }
            }
          })
        }
        return target[name];
      }
    });
  }
}

const a = new A;

a.try.method(); // no error

a.method(); // throws error

Live demo

如果template <typename T, typename = void> struct helper { using type = std::map<T, T>; }; template <typename T> struct helper<T, std::void_t<decltype(std::hash<T>())>> { using type = std::unordered_map<T, T>; }; 格式正确,则选择该专业化,否则SFINAE会启动并使用基本模板。然后,您可以将std::hash<T>()调整为如下所示:

myClass

请注意,在{+ C ++ 17>中添加了template<typename T> class myClass { //... using container = typename helper<T>::type; //... private: //... container m_mapping; //... }; 。如果您无法访问C ++ 17,则可以将自己的std::void_t实现为

void_t

答案 1 :(得分:2)

如果我说得对,那么在C ++ 11和更新版本中很容易实现。首先,您可以使用type_traits标题中的条件类型。它看起来像这样:

std::conditional<test, Type1, Type2>

如果test为真,则conditional的内部类型type等于Type1'. Otherwise, it has member type equal to Type2`。

其次,我们需要上面的test值,为此我们必须写一个特征。 我将从这里使用示例:How to decide if a template specialization exist - 让我们保持简单。

template<class T>
bool is_hashable_v = is_complete<std::hash<T>>::value;

编辑:上面不起作用。 我们先使用enable_if

template<class T, class = void>
struct is_hashable : std::false_type {};

template<class T>
struct is_hashable<
    T, 
    typename std::enable_if<decltype(std::hash<T>())>::type
> : std::true_type {};

template<class T>
bool is_hashable_v = is_hashable<T>::value;

如果你有is_hashable_v以上的话,那么就把它们放在一起:

using container =
    typename std::conditional<
        is_hashable_v<T>, 
        std::unordered_map<T, T>,
        std::map<T, T> 
    >::type;

答案 2 :(得分:0)

我意识到std::hash<T>不是静态函数,而是具有operator()和构造函数的结构。而不是试图检查是否存在std::hash<T>()(构造函数)或std::hash<T>()()(散列函数),我们只需检查std::hash<T> is_constructible

这使以下代码有效:

using container = typename std::conditional<
    std::is_constructible<std::hash<T>>::value,
    std::unordered_map<T,T>,
    std::map<T,T>
>::type;

答案 3 :(得分:0)

借助@Miles Budnek解决方案,我建议:

template <typename Key, typename Value, typename = void>
struct associative_map
: std::map<Key, Value> {
    using std::map<Key, Value>::map;
};

template <typename Key, typename Value>
struct associative_map<Key, Value, std::void_t<decltype(std::hash<Key>())>> 
: std::unordered_map<Key, Value> {
    using std::unordered_map<Key, Value>::unordered_map;
};

// Usage:

class Number {
    int _i;
public:
    Number(int i): _i(i) {}
    operator int()const {return _i;}
};

int main() {
    // associative_map below is std::unordered_map
    associative_map<int, std::string> int2string = { {1, "one"} };
    int2string[7] = "seven";
    for(const auto& p : int2string) {
        std::cout << p.first << ": " << p.second << std::endl;
    }

    // associative_map below is std::map
    associative_map<Number, std::string> number2string = { {1, "one"} };
    number2string[7] = "seven";
    for(const auto& p : number2string) {
        std::cout << p.first << ": " << p.second << std::endl;
    }
}

答案 4 :(得分:0)

使用C ++ 20

您可以使用概念:

template<typename Key>
concept Hashable = requires(Key a) {
    { std::hash<Key>{}(a) } -> std::convertible_to<std::size_t>;
};

template<typename Key>
concept Sortable = requires(Key a, Key b) {
    { a < b } -> std::convertible_to<bool>;
};

template<typename Key>
concept SortableNotHashable = Sortable<Key> && !Hashable<Key>;

template <typename Key, typename Value>
struct associative_map;

template <SortableNotHashable Key, typename Value>
struct associative_map<Key, Value>
: std::map<Key, Value> {
    using std::map<Key, Value>::map;
};

template <Hashable Key, typename Value>
struct associative_map<Key, Value> 
: std::unordered_map<Key, Value> {
    using std::unordered_map<Key, Value>::unordered_map;
};

代码:https://godbolt.org/z/3GjuXt


在C ++ 20之前

template <typename Key, typename Value, typename = void>
struct associative_map
: std::map<Key, Value> {
    using std::map<Key, Value>::map;
};

template <typename Key, typename Value>
struct associative_map<Key, Value, std::void_t<decltype(std::hash<Key>())>> 
: std::unordered_map<Key, Value> {
    using std::unordered_map<Key, Value>::unordered_map;
};

// Usage:

class Number {
    int _i;
public:
    Number(int i): _i(i) {}
    operator int()const {return _i;}
};

int main() {
    // associative_map below is std::unordered_map
    associative_map<int, std::string> int2string = { {1, "one"} };
    int2string[7] = "seven";
    for(const auto& p : int2string) {
        std::cout << p.first << ": " << p.second << std::endl;
    }
    // associative_map below is std::map
    associative_map<Number, std::string> number2string = { {1, "one"} };
    number2string[7] = "seven";
    for(const auto& p : number2string) {
        std::cout << p.first << ": " << p.second << std::endl;
    }
}