【问题标题】:Repeat template enable_if condition on multiple templates在多个模板上重复模板 enable_if 条件
【发布时间】:2020-09-11 19:33:51
【问题描述】:

我有以下代码...

#include <iostream>
#include <map>

template <typename T, typename...>
struct is_contained : std::false_type {};

template <typename T, typename Head, typename... Tail>
struct is_contained<T, Head, Tail...>
    : std::integral_constant<bool, std::is_same<T, Head>::value ||
                                       is_contained<T, Tail...>::value> {};

// Enum types
enum class Fruit { APPLE, BANANA };
 
// To string maps
std::map<Fruit, std::string> fruits = {{Fruit::APPLE, "apple"},
                                       {Fruit::BANANA, "banana"}};

// Map string map to enum
template <typename T>
std::map<T, std::string> typeMap;
template <>
std::map<Fruit, std::string> typeMap<Fruit> = fruits;

// operator<< for mapped enums
template <typename T,
          typename = std::enable_if_t<is_contained<T, Fruit>::value>>
std::ostream &operator<<(std::ostream &os, const T &t) {
  os << typeMap<T>.at(t);
  return os;
}

int main() {
  Fruit f = Fruit::BANANA;
  std::cout << f << std::endl;
  return 0;
}

... 为各种枚举类型实现operator&lt;&lt;。现在具体行:

typename = std::enable_if_t<is_contained<T, Fruit>::value>>

确保我的流运算符实现仅用于我的特定类型,该类型将扩展为我的所有用户枚举类型的列表。

我要解决的问题是如何在其他模板函数上重用该条件,而不必重复类型列表或使用宏。比如:typename = TypeIsInMyList&lt;T&gt;

可能很简单,但我没有想出一些东西。

is_contained 实现在 https://stackoverflow.com/a/16252940/11722. 中找到

【问题讨论】:

  • 如果你只是想为各种枚举实现operator&lt;&lt;,为什么还要为模板而烦恼呢?只需为每个类型依次实现operator&lt;&lt;,并将其放在与该类型相同的命名空间中,ADL 将完成剩下的工作。
  • 会有更多的功能,而不仅仅是 operator
  • 如果我确实需要它,我倾向于定义所有相关函数的特征类型,以及 typedef std::integral_constant&lt;bool, true&gt; traits_defined,我使用该 typedef 作为 enable_if 的一部分(非专用版本的 typedef 设置为 false)。但同样,如果您只是禁用函数,我发现在大多数情况下使用参数的重载函数是“更清洁”的解决方案。

标签: c++ templates c++14 template-meta-programming sfinae


【解决方案1】:

这是 C++20 的处理方式:

template <typename T, typename ...P>
concept one_of = (std::same_as<T, P> || ...);

那么你可以直接这样写:

template <one_of<Fruit, void, int, char> T>
std::ostream &operator<<(std::ostream &os, const T &t) {/*...*/}

或者提出另一个概念:

template <typename T>
concept foo = one_of<T, Fruit, void, int, char>;

template <foo T>
std::ostream &operator<<(std::ostream &os, const T &t) {/*...*/}

【讨论】:

  • 我肯定会尽可能使用概念,仍然在 C++17 上。
  • @Zitrax 然后你至少可以使用折叠表达式重写is_contained。类似template &lt;typename T, typename ...P&gt; constexpr bool is_contained = (std::same_as&lt;T, P&gt; || ...);
【解决方案2】:

如果我理解正确,您正在寻找一个简单的using,例如

template <typename T>
using TypeIsInMyList
   = std::enable_if_t<is_contained<T, Fruit, void, int, char>::value>;

你可以按如下方式使用

template <typename T,
          typename = TypeIsInMyList<T>>

但我建议修改如下

template <typename T>
using TypeIsInMyList
   = std::enable_if_t<is_contained<T, Fruit, void, int, char>::value, int>;

在等号的左边使用它

template <typename T,
          TypeIsInMyList<T> = 0>

这有点复杂,但是...在您的情况下,我认为这不是真正的区别,但是当您在重载中有替代功能时,第二种解决方案是一种更可取的方法。

【讨论】:

  • 我更喜欢使用std::nullptr_t 而不是int,这样用户就不能通过向第二个参数传递一些不同的值来滥用您的模板。
  • 完美,这行得通。我以为我尝试过类似的东西,但我一定是搞砸了。
  • @HolyBlackCat - 有趣的一点...我不是专家,但我记得对于将void * 用作模板值的类型是否合法存在疑问,所以...你是确定std::nullptr_t 是模板值参数的有效类型?
  • @max66 我不确定,但 GCC、Clang 和 MSVC 都接受它。 ¯\_(ツ)_/¯
  • @HolyBlackCat - 如果我理解正确,根据this page,应该是一个有效的类型......非常有趣
猜你喜欢
  • 2015-06-12
  • 1970-01-01
  • 2012-01-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-13
相关资源
最近更新 更多