如何约束模板参数以符合 std::map 中的键?

发布于 2024-12-21 15:46:29 字数 362 浏览 4 评论 0 原文

我有一个类模板,打算使用其参数 K 作为映射的键。

有没有办法限制模板参数为符合std::map中Key的类型?

我意识到即使没有这样的约束,编译器也会吐出一堆模板错误,例如K 没有运算符 < (),但如果我能让我的代码在指定需求时更加明显,那就太好了。

欢迎使用 C++11 解决方案。

template< typename K >
class Foo
{
  // lots of other code here...

  private:
    std::map< K, size_t > m_map;
};

I have a class template that intends to use its parameter K as the key to a map.

Is there any way to restrict the template parameter to be a type that complies with the Key in std::map?

I realize that even without such constraint, the compiler would spit out a pile of template errors like K having no operator < (), but it would be nice if I can make my code more obvious in specifying requirements.

C++11 solutions are welcome.

template< typename K >
class Foo
{
  // lots of other code here...

  private:
    std::map< K, size_t > m_map;
};

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

初雪 2024-12-28 15:46:29

这取决于您所说的“符合”是什么意思。如果您想验证 K 是否具有 < 运算符,那么您可以尝试 Boost 概念检查库

#include "boost/concept_check.hpp"

template< typename K >
class Foo
{
  BOOST_CONCEPT_ASSERT((boost::LessThanComparable< K >));

  // lots of other code here...

  private:
    std::map< K, size_t > m_map;
};

但是,如果您想验证 < 是否在 K 上定义了 StrictWeakOrdering,则需要在所有可能的输入下测试运行时行为。

It depends on what you mean by "complies." If you want to verify that K has a < operator then you might try the Boost Concept Checking Library.

#include "boost/concept_check.hpp"

template< typename K >
class Foo
{
  BOOST_CONCEPT_ASSERT((boost::LessThanComparable< K >));

  // lots of other code here...

  private:
    std::map< K, size_t > m_map;
};

However if you want to verify that < defines a StrictWeakOrdering on K, that would require testing run-time behaviour under all possible inputs.

梦情居士 2024-12-28 15:46:29

C++11 版本:

#include <type_traits>

template<class T>
struct satisfies_key_req{
  struct nat{};

  template<class K> static auto test(K* k) -> decltype(*k < *k);
  template<class K> static nat  test(...);

  static bool const value = !std::is_same<decltype(test<T>(0)), nat>::value;
};

#include <iostream>

struct foo{};

int main(){
    static bool const b = satisfies_key_req<int>::value;
    std::cout << b << '\n';
    static bool const b2 = satisfies_key_req<foo>::value;
    std::cout << b2 << '\n';
}

输出:

1
0

我在这里使用的关键点是表达式 SFINAE: auto test(K* k) -> decltype(*k < *k)。如果 Trailing-return-type 中的表达式无效,则 test 的这个特定重载将从重载集中删除。换句话说,它是SFINAE'd。

§14.8.2 [温度扣除]

p6 在模板参数推导过程中的某些时刻,有必要采用使用模板参数的函数类型,并将这些模板参数替换为相应的模板参数。这是在当任何显式指定的模板参数被替换为函数类型时,模板参数推导开始,并且当从默认参数推导或获取的任何模板参数被替换时,再次在模板参数推导结束。< /p>

p7 替换发生在函数类型和模板参数声明中使用的所有类型和表达式中。 表达式不仅包括常量表达式,例如出现在数组边界中或作为非类型模板参数的常量表达式,还包括通用表达式(即非常量表达式)内部 sizeofdecltype 以及其他允许非常量表达式的上下文。

p8 如果替换导致无效类型或表达式,则类型推导失败。无效类型或表达式是指如果使用替换参数编写的类型或表达式,其格式会不正确。 [...]


您可以在 Foo 类中以三种方式使用它来触发错误。

// static_assert, arguably the best choice
template< typename K >
class Foo
{
  static_assert<satisfies_key_req<K>::value, "K does not satisfy key requirements");
  // lots of other code here...

  private:
    std::map< K, size_t > m_map;
};

// new-style SFINAE'd, maybe not really clear
template<
  typename K,
  typename = typename std::enable_if<
               satisfies_key_req<K>::value
             >::type
>
class Foo
{
  // lots of other code here...

  private:
    std::map< K, size_t > m_map;
};

// partial specialization, clarity similar to SFINAE approach
template< 
  typename K,
  bool = satisfies_key_req<K>::value
>
class Foo
{
  // lots of other code here...

  private:
    std::map< K, size_t > m_map;
};

template<typename K>
class Foo<K, false>;

C++11 version:

#include <type_traits>

template<class T>
struct satisfies_key_req{
  struct nat{};

  template<class K> static auto test(K* k) -> decltype(*k < *k);
  template<class K> static nat  test(...);

  static bool const value = !std::is_same<decltype(test<T>(0)), nat>::value;
};

#include <iostream>

struct foo{};

int main(){
    static bool const b = satisfies_key_req<int>::value;
    std::cout << b << '\n';
    static bool const b2 = satisfies_key_req<foo>::value;
    std::cout << b2 << '\n';
}

Output:

1
0

The key point I used here is expression SFINAE: auto test(K* k) -> decltype(*k < *k). If the expression in the trailing-return-type is not valid, then this particular overload of test is removed from the overload set. In other words, it's SFINAE'd.

§14.8.2 [temp.deduct]

p6 At certain points in the template argument deduction process it is necessary to take a function type that makes use of template parameters and replace those template parameters with the corresponding template arguments. This is done at the beginning of template argument deduction when any explicitly specified template arguments are substituted into the function type, and again at the end of template argument deduction when any template arguments that were deduced or obtained from default arguments are substituted.

p7 The substitution occurs in all types and expressions that are used in the function type and in template parameter declarations. The expressions include not only constant expressions such as those that appear in array bounds or as nontype template arguments but also general expressions (i.e., non-constant expressions) inside sizeof, decltype, and other contexts that allow non-constant expressions.

p8 If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is one that would be ill-formed if written using the substituted arguments. [...]


You can use it in three flavors for your Foo class to trigger an error.

// static_assert, arguably the best choice
template< typename K >
class Foo
{
  static_assert<satisfies_key_req<K>::value, "K does not satisfy key requirements");
  // lots of other code here...

  private:
    std::map< K, size_t > m_map;
};

// new-style SFINAE'd, maybe not really clear
template<
  typename K,
  typename = typename std::enable_if<
               satisfies_key_req<K>::value
             >::type
>
class Foo
{
  // lots of other code here...

  private:
    std::map< K, size_t > m_map;
};

// partial specialization, clarity similar to SFINAE approach
template< 
  typename K,
  bool = satisfies_key_req<K>::value
>
class Foo
{
  // lots of other code here...

  private:
    std::map< K, size_t > m_map;
};

template<typename K>
class Foo<K, false>;
一页 2024-12-28 15:46:29

完全解决您的问题可能是不可能的。但我可以建议你一个解决方案,如果你想将 K 限制为特定类型,你可以按如下方式定义你的类

template <class K, bool = std::is_same<K,int>::value>
class Foo { ... };

template <class K>
class Foo<K,false> { Foo(); };

所以如果 Kint 你的类按预期工作。否则,您无法构造 Foo 类型的对象。显然你可以完善条件。

要检查 K 是否具有 operator<,您可以使用 boost::has_less,但您可以在 文档此特征并不总是能正常工作。

A complete solution to your problem probably is impossible. But I can suggest you a solution, if you want to restrict K to be a specific type you can define your class as follows

template <class K, bool = std::is_same<K,int>::value>
class Foo { ... };

template <class K>
class Foo<K,false> { Foo(); };

So if K is int your class works as expected. Otherwise you can not costruct object of type Foo<K>. Obviously you can refine the condition.

To check if K has the operator< you can use boost::has_less, but as you can check in the documentation this traits doesn't work properly always.

无人问我粥可暖 2024-12-28 15:46:29

从你的问题来看,我猜你想要一个合适的错误,而不是一大堆错误。

以下代码查找类型是否具有有效的运算符<

namespace OperatorExist
{
  typedef char no[3]; // '3' can be any awkward number
  template<typename T> no& operator < (const T&, const T&);

  template<typename T>
  struct LessThan {
    static const bool value = (sizeof(*(T*)(0) < *(T*)(0)) != sizeof(no));
  };
}

现在,您可以使用上述构造专门化class Foo

template<typename K, bool = OperatorExist::LessThan<K>::value>
class Foo 
{
  // lots of other code here...
  private:
    std::map<K, size_t> m_map;
};

template<typename K>
class Foo<K, false>; // unimplemented for types not suitable for 'std::map'

只要您使用某种类型< code>A 与 std::map 不兼容,编译器会报错:

error: aggregate ‘Foo<A> oa’ has incomplete type and cannot be defined

演示

From your question, I guess you want one suitable error instead of loads of errors.

Following code finds, if a type has a valid operator < or not:

namespace OperatorExist
{
  typedef char no[3]; // '3' can be any awkward number
  template<typename T> no& operator < (const T&, const T&);

  template<typename T>
  struct LessThan {
    static const bool value = (sizeof(*(T*)(0) < *(T*)(0)) != sizeof(no));
  };
}

Now, you can specialize class Foo using the above construct:

template<typename K, bool = OperatorExist::LessThan<K>::value>
class Foo 
{
  // lots of other code here...
  private:
    std::map<K, size_t> m_map;
};

template<typename K>
class Foo<K, false>; // unimplemented for types not suitable for 'std::map'

As soon as you use some type A which doesn't have compatibility with std::map, the compiler will complain with single error:

error: aggregate ‘Foo<A> oa’ has incomplete type and cannot be defined

Demo

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文