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

How do I constrain a template parameter to comply to a Key in std::map?

本文关键字:std map Key 中的 参数 何约束 约束      更新时间:2023-10-16

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

是否有办法将模板形参限制为符合std::map中的Key的类型?

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

欢迎c++ 11解决方案。

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

这取决于你所说的"服从"是什么意思。如果您想验证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,那将需要在所有可能的输入下测试运行时行为。

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)。如果末尾返回类型中的表达式无效,则从重载集中删除test的这个特定重载。换句话说,它是SFINAE'd。

§14.8.2 [temp.deduct]

p6 在模板实参推导过程中的某些地方,有必要采用使用模板形参的函数类型,并用相应的模板实参替换这些模板形参。当任何显式指定的模板实参被替换为函数类型时,在模板实参演绎开始时执行,当任何从默认实参推导或获得的模板实参被替换时,在模板实参演绎结束时再次执行

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

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>;

一个完整的解决方案可能是不可能的。但我可以建议你一个解决方案,如果你想限制K是一个特定的类型,你可以定义你的类如下

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

所以如果Kint你的类工作如预期。否则你不能构造类型为Foo<K>的对象。显然你可以改善条件。

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

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

下面的代码查找类型是否有有效的operator <:

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'

只要你使用了与std::map不兼容的A类型,编译器就会报错:

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