首页 > 解决方案 > 模棱两可的偏特化和 enable_if_t

问题描述

这个问题是由于疯狂的好奇心而不是实际问题。考虑以下代码

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<type_list>' 的模棱两可的偏特化

如果我将第二个专业化更改为从功能角度来看不起作用的东西,错误就会消失

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

同样,如果我使用别名模板void_t一切都会按预期工作

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_tand的丑陋之外enable_if_t,当存在与 不同的单一类型时,这也可以完成工作int,即对于 a static_assert(!test_class<type_list<char>>::value)(它不会在第二种情况下工作,原因很明显)。

我明白为什么第三种情况有效,因为当满足条件并且比(对吗?)更专业时,别名模板实际上被替换为。但是,我对以下内容也有同样的期望:voidenable_if_ttype_list<T>type_list<T...>

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<std::is_same_v<T, int>>不知何故 void当条件得到满足时(好吧,从技术上讲,它是typename blabla::type,被授予但::type仍然不是void?),因此我不明白为什么它会导致一个模棱两可的电话。我很确定我在这里遗漏了一些明显的东西,我现在很想理解它。

如果您能指出这方面的标准,我会很高兴,并让我知道是否存在比合并void_tenable_if_t最终更好的解决方案。

标签: c++templatesc++17sfinaetemplate-specialization

解决方案


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

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

在这里试试!

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

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

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

例如std::enable_if_t<std::is_same_v<T, T>>andvoid仅在功能上等效:第一项将被评估void为任何模板参数T。这意味着根据包含两个特化的[temp.over.link]/7 代码<T, void>并且<T, std::enable_if_t<std::is_same_v<T, T>>已经是格式错误的:

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

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

当现在执行部分排序 [temp.func.order]以查看哪个专业化与您的调用最匹配时,这将导致歧义,因为在两种情况下test_class同样专业化[temp.func.order]/6(使用Ts={int}or T=int, void),因此编译会失败。

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

template <typename T>
using void_t = void;

部分排序将成功,因为在这种情况下,编译器已经知道最后一个模板参数的类型void在所有情况下都是,选择test_class<T, std::void_t<std::enable_if_t<std::is_same_v<T,int>>>>withT=int作为最专业的。


推荐阅读