【问题标题】:handle logical "OR" for predicates in std::enable_if<...>处理 std::enable_if<...> 中谓词的逻辑“或”
【发布时间】:2020-06-08 14:57:57
【问题描述】:

说,我的方法有这个返回类型定义(模板和方法无关,因此未显示),这需要为enable_if 谓词处理逻辑“或”:

typename std::enable_if<{predicate_1} or {predicate_2}), void>::type>
my_method(...) { ... }

当然它不会起作用,因为一旦任何一个谓词替换失败,这个实例就会被清除。

我想到的第一个快速而肮脏的解决方案是提出一个定义,将失败的谓词评估为false,例如,像这样:

template<bool, typename T = void>
struct failed_2_false: std::false_type {};
template<typename T>
struct failed_2_false<true, T>: std::true_type {};

那么这将起作用:

typename std::enable_if<failed_2_false<{predicate_1}>::value or failed_2_false:<{predicate_2}>::value,
                        void>::type>
my_method(...) { ... }

所以,我的问题是: - 什么/是否有标准 (STL) 惯用方式来处理谓词的逻辑“OR”?

【问题讨论】:

  • 我有点困惑。不是enable_if什么失败了,不是谓词吗?我的意思是,如果您不需要enable_if 的谓词替换已经失败?我可能错了,我习惯了 enable_if 的参数不会失败,但要么评估为 true 要么 false
  • 你能举个例子吗?
  • @idclev463035818,好吧,首先使 enable_if 失败的谓词失败。一个简单的解决方案(除了所示)是在enable_if 中拥有两个带有每个谓词的方法实例。但这将是代码重复和空间浪费。更好的是使用enable_if 下的逻辑OR 联结来组合两个谓词(显示的解决方案确实有效)
  • 如果std::is_same&lt;typename X::type, int&gt;{} 失败,那么既不是std::enable_if 的错,也不是std::is_same 的错。你需要确切地知道你的谓词是什么并解决它们

标签: c++ templates


【解决方案1】:

假设您正在使用这样的 SFINAE 来检查一个类型是否具有 T::barT::type 并且它们中的任何一个都应该是 int

template <typename T>
typename std::enable_if<
    std::is_same<typename T::bar,int>::value ||
    std::is_same<typename T::type,int>::value, void>::type asdf(){ std::cout << "0";}

template <typename T>
typename std::enable_if<
    !std::is_same<typename T::bar,int>::value &&
    !std::is_same<typename T::type,int>::value, void>::type asdf(){ std::cout << "1";}

这行不通,因为当T 没有T::barT::type 时,std::is_same&lt;...&gt; 已经无法替换。

解决方案是使用在替换时不会失败的谓词。基于this answer,我们可以使用下面的检测习语来检测一个类型是否有一些属性:

// See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf.
template <typename...>
using void_t = void;

// Primary template handles all types not supporting the operation.
template <typename, template <typename> class, typename = void_t<>>
struct detect : std::false_type {};

// Specialization recognizes/validates only types supporting the archetype.
template <typename T, template <typename> class Op>
struct detect<T, Op, void_t<Op<T>>> : std::true_type {};

只是为了说明,检测一个类型是否有T::type

template <typename T>
using has_type_t = typename T::type;

template <typename T>
using has_type = detect<T, has_type_t>;

T::type 成为某种类型只是有点棘手:

template <typename X>
struct has_X_type_helper {
    template <typename T>
    using type = typename std::enable_if_t<std::is_same_v< typename T::type, X>,int>;
};

template <typename T,typename X>
using has_X_type = detect<T,has_X_type_helper<X>::template type>;

类型特征需要一些样板,我们必须为 T::bar 编写相同的代码:

template <typename X>
struct has_X_bar_helper {
    template <typename T>
    using type = typename std::enable_if_t<std::is_same_v< typename T::bar, X>,int>;
};

template <typename T,typename X>
using has_X_bar = detect<T,has_X_bar_helper<X>::template type>;

请注意,特征 has_X_typehas_X_bar 确实使用 SFINAE,但这是一个实现细节。这些特征在替换时不会失败(除非它们的参数已经失败)但它们评估为std::true_typestd::false_type。现在上面的SFINAE可以用||&amp;&amp;来实现了:

template <typename T>
typename std::enable_if<
    has_X_type<T,int>::value ||
    has_X_bar<T,int>::value, void>::type asdf(){ std::cout << "0";}

template <typename T>
typename std::enable_if<
    !has_X_type<T,int>::value &&
    !has_X_bar<T,int>::value, void>::type asdf(){ std::cout << "1";}

Live example @ godbolt

PS:你所说的“又快又脏”并不脏。它快速,干净,可以完成工作。如果您使用计算结果为 std::true_typestd::false_type 的谓词,则可以使用普通布尔运算符来构造更复杂的谓词。

【讨论】:

  • 感谢您为提出解释和示例所做的努力,并将其作为答案
  • @Dmitry 我不精通 SFINAE,但我渴望更好地理解它,所以我试图提出一个可能会出现问题的示例,这就是我将用作解决方案的示例。对于什么是“惯用的”,我真的不是合适的人;)。这次演讲对我来说是一个巨大的启示,他以一种基本而简单的方式解释了非常有用的东西:youtube.com/watch?v=PFdWqa68LmA
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-28
  • 1970-01-01
  • 2016-08-12
  • 1970-01-01
  • 1970-01-01
  • 2020-10-18
相关资源
最近更新 更多