【问题标题】:Ambiguous partial specializations and enable_if_t模棱两可的偏特化和 enable_if_t
【发布时间】:2021-10-06 18:24:40
【问题描述】:

这个问题是出于疯狂的好奇心,而不是实际问题。 考虑the following code

template<typename...>
struct type_list {};

template<typename, typename = void>
struct test_class;

template<typename... T>
struct test_class<type_list<T...>> {
    static constexpr auto value = false;
};

template<typename T>
struct test_class<type_list<T>, std::enable_if_t<std::is_same_v<T, int>>> {
    static constexpr auto value = true;
};

int main() {
    static_assert(!test_class<type_list<double, char>>::value);
    static_assert(test_class<type_list<int>>::value);
}

这失败并出现错误:

'test_class' 的模糊偏特化

如果我将第二个专业化更改为从功能角度来看不起作用的东西,the error would go away:

template<typename T>
struct test_class<type_list<T>> {
    static constexpr auto value = true;
};

同样,如果我使用别名模板void_teverything works as expected

template<typename T>
struct test_class<type_list<T>, std::void_t<std::enable_if_t<std::is_same_v<T, int>>>> {
    static constexpr auto value = true;
};

除了组合void_tenable_if_t 的丑陋之外,当存在与int 不同的单一类型时,这也可以完成工作,即对于static_assert(!test_class&lt;type_list&lt;char&gt;&gt;::value)(它不会在第二种情况,原因很明显)。

我明白为什么第三种情况works-ish,因为当满足enable_if_t 的条件并且@ 987654336@ 比 type_list&lt;T...&gt; 更专业(对吗?)。 但是,我对以下内容也有同样的期望:

template<typename T>
struct test_class<type_list<T>, std::enable_if_t<std::is_same_v<T, int>>> {
    static constexpr auto value = true;
};

在一天结束时,std::enable_if_t&lt;std::is_same_v&lt;T, int&gt;&gt; 不知何故 void 当条件满足时(好吧,从技术上讲它是typename blabla::type,被授予但不是::type 仍然@ 987654343@?),因此我不明白为什么它会导致模棱两可的电话。我很确定我在这里遗漏了一些明显的东西,我现在很想理解它。

如果您能为此指出 standardese 并告诉我是否存在比最终组合 void_tenable_if_t 更好的解决方案,我会很高兴。

【问题讨论】:

  • 仅供参考(因为您的链接是用 C++20 编译的)它适用于约束 godbolt.org/z/PWrEMqKEv
  • 哦,我的错,对不起。首先,感谢您的反馈,因为了解 C++20 总是好的。但是,它是错误地用 C++20 编译的。 :)§虽然,是的,很高兴知道我将来能够摆脱丑陋!特别是如果我今天找不到更好的东西。
  • 我不认为type_list&lt;T&gt;type_list&lt;T...&gt; 更专业。它们在 1 个参数上都可以很好地匹配。我觉得这个问题有点落后。我更惊讶的是,当您使用void_t 时它确实有效。我原以为您需要禁用第一个专业化才能完全避免混淆(例如here)。
  • 嗯,我认为确实如此。非正式地,可变参数形式比单一类型形式满足更多的情况。除此之外,我已经使用这个技巧无数次了,现在发现 它不起作用 会令人震惊。所以,让我们在这里停止讨论! :D 无论如何,为了简洁起见,我使用了可变参数模板,但问题可以很容易地映射到不使用它的示例。所以,抛开玩笑,请不要专注于它。

标签: c++ templates c++17 sfinae template-specialization


【解决方案1】:

让我们从代码的扩展版本开始

template<typename, typename = void>
struct test_class;

template<typename T>
struct test_class<type_list<T>> {
  static constexpr auto value = false;
};

template<typename... Ts>
struct test_class<type_list<Ts...>> {
  static constexpr auto value = false;
};

template<typename T>
struct test_class<type_list<T>, std::enable_if_t<std::is_same_v<T, int>>> {
  static constexpr auto value = true;
};

被调用的

test_class<type_list<int>>::value

Try it here!

标准区分模板参数是等价的、仅在功能上等价的和不等价的[temp.over.link]/5

如果两个包含表达式的函数定义满足单定义规则,则两个涉及模板参数的表达式被认为是等价的,除非用于命名模板参数的标记可能不同,只要用于命名模板参数的标记在一个表达式被另一个标记替换,该标记在另一个表达式中命名相同的模板参数。 如果两个包含表达式的函数定义满足单一定义规则,则两个不涉及模板参数的未计算操作数被认为是等价的,除非用于命名类型和声明的标记可能不同,只要它们命名相同的实体,并且只要两个模板 ID 相同([temp.type]),用于形成概念 ID 的标记就可以不同。

如果对于任何给定的模板参数集,表达式的求值结果相同,则两个涉及不等价的模板参数的潜在求值表达式在功能上是等价的。 如果对于任何给定的模板参数集,表达式以相同的顺序对相同的实体执行相同的操作,则两个不等价的未计算操作数在功能上是等价的。

例如std::enable_if_t&lt;std::is_same_v&lt;T, T&gt;&gt;void 仅在功能上等效:对于任何模板参数 T,第一项将被评估为 void。这意味着根据[temp.over.link]/7 code containing two specialisations &lt;T, void&gt; and &lt;T, std::enable_if_t&lt;std::is_same_v&lt;T, T&gt;&gt; 已经是病态的:

如果程序的有效性或意义取决于两个构造是否等价,并且它们在功能上等价但不等价,则该程序是非良构的,不需要诊断。

在上面的代码中,std::enable_if_t&lt;std::is_same_v&lt;T, int&gt;&gt; 在功能上甚至不等同于任何其他版本,因为它通常不等同于void

现在performing partial ordering [temp.func.order] 来查看哪个专业化与您的调用最匹配,这将导致歧义,因为test_class 在这两种情况下都同样专业化[temp.func.order]/6(@987654341 @ 或 T=int, void),因此编译将失败。

另一方面,用std::void_t 包装std::enable_if_t,这不过是void 的别名

template <typename T>
using void_t = void;

部分排序将成功,因为在这种情况下编译器已经know the type of the last template parameter is void in all cases,选择test_class&lt;T, std::void_t&lt;std::enable_if_t&lt;std::is_same_v&lt;T,int&gt;&gt;&gt;&gt;T=int 作为最专业的。

【讨论】:

  • 这有点对,但它是部分排序失败,而不是(整体)等价失败。
  • 老实说,在这种情况下,我支持@DavisHerring。我不认为你说的是​​错的,但我觉得这不是我问题的正确答案。或者至少,在我看来,它缺少一些东西来完全解决这个问题。不知道怎么解释,只是我的直觉。
  • 噢,@DavisHerring 我现在看到了。使用enable_if 时,虚构的函数模板会有所不同,并且在进行偏序时它们都满足两种方式。是吗?
  • @2b-t 是的,正如我所说:我不认为这是错误的,而是感觉不完整,部分排序部分是触发我的原因。 :)
  • @skypjack:他们满意,因为两者都不能从另一个推导出来(这是部分排序失败):T 不能从T’...std::enable_if_t&lt;…&gt; 不能在另一个方向上与 void 进行检查。由于这些决定是在不考虑实际模板参数的情况下做出的(在使用它们过滤匹配的部分特化之后),因此它们在功能上并不等效。
猜你喜欢
  • 2015-10-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-05-10
  • 1970-01-01
  • 2021-07-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多