c++ - 模棱两可的偏特化和 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_t
and的丑陋之外enable_if_t
,当存在与 不同的单一类型时,这也可以完成工作int
,即对于 a static_assert(!test_class<type_list<char>>::value)
(它不会在第二种情况下工作,原因很明显)。
我明白为什么第三种情况有效,因为当满足条件并且比(对吗?)更专业时,别名模板实际上被替换为。但是,我对以下内容也有同样的期望:void
enable_if_t
type_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_t
和enable_if_t
最终更好的解决方案。
解决方案
让我们从代码的扩展版本开始
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_t
,std::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
作为最专业的。
推荐阅读
- ios - 如何修复 _ADBannerContentSizeIdentifierLandscape、非公共符号
- c++ - 如何创建一个接受值向量的模板函数,并指定向量类型?
- powershell - 将 AzureAD 用户添加到 AzureAD 组
- c++ - 当我终止进程时,它也会终止线程吗?
- javascript - 在 npm lib 中使用的 rxjs 应该是依赖关系、对等依赖关系还是两者兼而有之?
- python - 带等高线图的时间轴。Python
- amazon-web-services - ELB 和 Route 53 健康检查之间的区别
- php - 正则表达式,排除 html 中的文件名
- coinbase-api - 查询Coinbase Pro Stablcoin转换历史
- android - Android 中的 Azure Cosmos DB 连接?