【问题标题】:Template template class predicate not working in partial specialization模板模板类谓词不适用于部分专业化
【发布时间】:2018-11-28 06:38:15
【问题描述】:

我有许多EnableIf 特征,基本上检查输入类型是否满足接口。我试图创建一个通用的 Resolve 特征,可用于将它们转换为布尔特征。

类似这样的东西 - https://wandbox.org/permlink/ydEMyErOoaOa60Jx

template <
  template <typename...> class Predicate,
  typename T,
  typename = std::void_t<>>
struct Resolve : std::false_type {};
template <template <typename...> class Predicate, typename T>
struct Resolve<Predicate, T, Predicate<T>> : std::true_type {};

现在如果你有这样的EnableIf trait

template <typename T>
using EnableIfHasFoo = std::void_t<decltype(std::declval<T>().foo())>;

您可以非常快速地创建一个布尔版本

template <typename T>
struct HasFoo : Resolve<EnableIfHasFoo, T> {};

或者类似的变量模板。

但由于某种原因,部分专业化没有按预期工作。解决无法按预期工作。在此处查看输出 - https://wandbox.org/permlink/ydEMyErOoaOa60Jx。 “手动”实现的相同功能 - https://wandbox.org/permlink/fmcFT3kLSqyiBprm

我正在求助于自己手动定义类型。是否有我遗漏的部分特化和模板模板参数的细节?

【问题讨论】:

  • 我找不到原因,但您需要部分专业化为Resolve&lt;Predicate, T, std::void_t&lt;Predicate&lt;T&gt;&gt;。那么此时也可以去掉别名中的std::void_t
  • 顺便说一句,你的代码看起来很像dectection idiom
  • @GuillaumeRacicot 但是谓词是什么?只是 decltype()?很不错!此外,令人困惑的是为什么上述方法不起作用。
  • 它可以是一个别名模板来简单地decltype。我不知道为什么,但void_t 必须是部分专业化的。我认为这与部分排序的工作方式有关。

标签: c++ templates c++17 sfinae enable-if


【解决方案1】:

我找不到您的示例不起作用的确切原因。如果您想深入了解std::void_t 的详细信息,请查看interesting explanation

即使我无法深入解释它,我也想添加另一种可靠的语法,用于detection idiom

template<
    template <typename...> class Predicate,
    typename T,
    typename = void>
struct Resolve : std::false_type {};

template <template <typename...> class Predicate, typename T>
struct Resolve<Predicate, T, std::void_t<Predicate<T>>> : std::true_type {};

template <typename T>
using EnableIfHasFoo = decltype(std::declval<T>().foo());

live on compiler explorer

【讨论】:

  • 对最小的改变投赞成票。但我删除了我的,因为我还在猜测原因。
  • @StoryTeller 谢谢。我也没有找到原因。这可能是因为该规则并未使专业化更加专业化。
  • 可能是。我目前正在研究偏序以了解原因。
  • @StoryTeller 我添加了指向另一个解释 void_t 的答案的链接。然而,即使是那个答案似乎也不能完全解释你的情况。
  • 这是我尝试做的一个有趣的尝试。谢谢。
【解决方案2】:

您的方法失败的原因是第三个模板参数中的Predicate&lt;T&gt;&gt; 不是非推断上下文。这会导致推导直接失败(请参阅[temp.alias]/2),而不是像在非推导上下文中那样使用从其他地方推导的模板参数。

您可以将 Predicate&lt;T&gt;&gt; 包装到非推断上下文中以使其工作:

template<class T>
struct identity {
    using type = T;
};

template <template <typename...> class Predicate, typename T>
struct Resolve<Predicate, T, typename identity<Predicate<T>>::type> : std::true_type {};

Live Demo

因为在非推导上下文中,Predicate&lt;T&gt; 部分不会进行推导,而是使用从其他地方获得的PredicateT

至于为什么通常的检测成语(见Guillaume Racicot's answer)会起作用,是因为std::void_t作为模板别名,在演绎阶段会被void替换(见[temp.alias]/2),因此没有会扣分。

这里有一些例子可以更清楚地说明:

template<class T>
using always_int = int;

template<template<class> class TT>
struct deductor {};

template<template<class> class TT, class T>
void foo(T, deductor<TT>) {}

template<template<class> class TT, class T>
void bar(T, deductor<TT>, TT<T>) {}

template<class T>
void baz(T, always_int<T>) {}

int main() {
    // ok, both T and TT are deduced
    foo(0, deductor<always_int>{});

    // ERROR, TT<T> is NOT a non-deduced context, deduction failure
    bar(0, deductor<always_int>{}, 0);

    // ok, T is deduced, always_int<T> is replaced by int so no deduction
    baz(0, 0);
}

【讨论】:

  • 那么......为什么它通常与void_t一起工作?它也是一个别名模板。
  • @StoryTeller 因为推演是从std::void_t&lt;Predicate&lt;T&gt;&gt;&gt;开始的,所以std::void_t是已知的。
  • 抱歉,我的意思通常是std:void_t&lt;decltype(std::declval&lt;T&gt;().foo)&gt;
  • 是的,它是(真的应该打印那个项目符号列表)。但是,别名模板特化的等价难道不应该让这件事变得没有意义吗?也就是说,应该是 OP 直接写了std::void_t&lt;declval&lt;...&gt;(...).foo&gt;,不是吗?抱歉,问题泛滥,我只是盯着这些段落太久了。
  • 哦。不用等。我现在明白了。很好的答案!我们隐藏的 void_t 立即失败,因为特化 需要 被其等效类型替换!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-23
  • 1970-01-01
  • 2015-08-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多