忽略 C++20 概念特化中的模板实例化失败
Ignoring template instantiation failures in C++20 concept specializations
假设我想写一个 class Collect
,它为我提供了将给定 std::vector<>
的项目转移到任何类型的集合的功能。
所有项目类型都支持转移到可向后插入的集合,但转移到关联集合(unordered_map,地图)仅
如果项目是 std::pair<,>
.
的变体,则支持
为了实现这一点,我首先定义了标识所请求容器类型的概念(为清楚起见,已缩短且不完整):
/** Concept that checks whether the given type is a std::pair<> */
template<typename T> concept is_pair = requires(T pair) {
typename T::first_type;
typename T::second_type;
{std::get<typename T::first_type>(pair)} -> std::convertible_to<typename T::first_type>;
{std::get<typename T::second_type>(pair)} -> std::convertible_to<typename T::second_type>;
};
/** Concept that checks whether the given type is a back-insertable collection */
template<template<typename...> typename TContainer, typename TItem>
concept BackInsertableCollection = requires(TContainer<TItem> container, TItem item) {
typename decltype(container)::value_type;
container.push_back(item);
};
/** Concept that checks whether the given type is an associative collection */
template<template<typename...> typename TContainer, typename TItemKey, typename TItemValue>
concept AssocCollection = requires(TContainer<TItemKey, TItemValue> container, TItemKey key, TItemValue value) {
typename decltype(container)::value_type;
typename decltype(container)::key_type;
typename decltype(container)::mapped_type;
container[key] = value;
};
然后,我开始实现我上面描述的 Collect
class,首先定义它的空模板形式:
/** Collector class that takes a std::vector<TItem> as argument,
* and produces a new container of the requested type TContainer,
* containing the items from the vector.
*/
template<template<typename...> typename TContainer, typename TItem>
struct Collect {};
然后我使用上面定义的概念创建这个 class 的专业化:
/** Collect specialization for BackInsertable containers */
template<template<typename...> typename TContainer, typename TItem>
requires BackInsertableCollection<TContainer, TItem>
struct Collect<TContainer, TItem> {
static auto collect(std::vector<TItem>& values) {
TContainer<TItem> container;
for(const auto& value : values) {
container.push_back(value);
}
return container;
}
};
/** Collect specialization for associative containers */
/** Requires items to be std::pair<>, automatically extracting key and value type for container */
template<template<typename...> typename TContainer, typename TItem>
requires is_pair<TItem> && AssocCollection<TContainer, typename TItem::first_type, typename TItem::second_type>
struct Collect<TContainer, TItem> {
static auto collect(std::vector<TItem>& values) {
TContainer<typename TItem::first_type, typename TItem::second_type> container;
for(const auto& value : values) {
container[value.first] = value.second;
}
return container;
}
};
将非std::pair<,>
物品收集到可向后插入的容器中效果很好。将 std::pair<>
项目收集到关联容器也是如此。
但是,将 std::pair<>
项收集到 std::vector<>
中会导致编译器错误:
int main() {
{ // works: non-std::pair<> to back-insertible container
using Item = std::string;
std::vector<Item> input = {"1", "2"};
auto output = Collect<std::vector, Item>::collect(input);
}
{ // works: std::pair<> to associative container
using Item = std::pair<int, std::string>;
std::vector<Item> input = {{1, "1"}, {2, "2"}};
auto output = Collect<std::unordered_map, Item>::collect(input);
}
{ // compiler error: std::pair<> to back-insertible container
using Item = std::pair<int, std::string>;
std::vector<Item> input = {{1, "1"}, {2, "2"}};
auto output = Collect<std::vector, Item>::collect(input);
}
return 0;
}
最后一个案例在 std::vector
中遇到了 static_assert
失败,因为 AssocCollection
概念采用了来自 std::pair<,>
的 [=26] 的键和值类型=],以及 second_type
。如果它这样做并将这两种类型作为 std::vector
的第一个和第二个模板参数,它会为 std::vector<>
的分配器模板参数提供一个无效参数。
这是带有编译器错误的完整示例的 link:https://godbolt.org/z/oY3cfbrYE
我原以为这些概念在这里的行为更像 SFINAE,这样的错误会导致模板特化被排除,而不是导致编译器错误。有没有比回到 SFINAE 更 C++20`ish 的方法来让它工作?
Is there a more C++20`ish way to get this to work, than falling back
to SFINAE?
问题在于,当TContainer
对可插入集合进行建模时,Collect
对关联集合的偏特化仍然会实例化约束,这会导致实例化无效,例如std::vector<int,std::string>
触发 static_assert
.
您可以向此偏特化添加额外的约束,以在满足前一个偏特化时阻止其实例化。
/** Collect specialization for associative containers */
template<template<typename...> typename TContainer, typename TItem>
requires (!BackInsertableCollection<TContainer, TItem>) &&
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
is_pair<TItem> && AssocCollection<TContainer, typename TItem::first_type, typename TItem::second_type>
struct Collect<TContainer, TItem> {
static auto collect(std::vector<TItem>& values) {
TContainer<typename TItem::first_type, typename TItem::second_type> container;
for(const auto& value : values) {
container[value.first] = value.second;
}
return container;
}
};
假设我想写一个 class Collect
,它为我提供了将给定 std::vector<>
的项目转移到任何类型的集合的功能。
所有项目类型都支持转移到可向后插入的集合,但转移到关联集合(unordered_map,地图)仅
如果项目是 std::pair<,>
.
为了实现这一点,我首先定义了标识所请求容器类型的概念(为清楚起见,已缩短且不完整):
/** Concept that checks whether the given type is a std::pair<> */
template<typename T> concept is_pair = requires(T pair) {
typename T::first_type;
typename T::second_type;
{std::get<typename T::first_type>(pair)} -> std::convertible_to<typename T::first_type>;
{std::get<typename T::second_type>(pair)} -> std::convertible_to<typename T::second_type>;
};
/** Concept that checks whether the given type is a back-insertable collection */
template<template<typename...> typename TContainer, typename TItem>
concept BackInsertableCollection = requires(TContainer<TItem> container, TItem item) {
typename decltype(container)::value_type;
container.push_back(item);
};
/** Concept that checks whether the given type is an associative collection */
template<template<typename...> typename TContainer, typename TItemKey, typename TItemValue>
concept AssocCollection = requires(TContainer<TItemKey, TItemValue> container, TItemKey key, TItemValue value) {
typename decltype(container)::value_type;
typename decltype(container)::key_type;
typename decltype(container)::mapped_type;
container[key] = value;
};
然后,我开始实现我上面描述的 Collect
class,首先定义它的空模板形式:
/** Collector class that takes a std::vector<TItem> as argument,
* and produces a new container of the requested type TContainer,
* containing the items from the vector.
*/
template<template<typename...> typename TContainer, typename TItem>
struct Collect {};
然后我使用上面定义的概念创建这个 class 的专业化:
/** Collect specialization for BackInsertable containers */
template<template<typename...> typename TContainer, typename TItem>
requires BackInsertableCollection<TContainer, TItem>
struct Collect<TContainer, TItem> {
static auto collect(std::vector<TItem>& values) {
TContainer<TItem> container;
for(const auto& value : values) {
container.push_back(value);
}
return container;
}
};
/** Collect specialization for associative containers */
/** Requires items to be std::pair<>, automatically extracting key and value type for container */
template<template<typename...> typename TContainer, typename TItem>
requires is_pair<TItem> && AssocCollection<TContainer, typename TItem::first_type, typename TItem::second_type>
struct Collect<TContainer, TItem> {
static auto collect(std::vector<TItem>& values) {
TContainer<typename TItem::first_type, typename TItem::second_type> container;
for(const auto& value : values) {
container[value.first] = value.second;
}
return container;
}
};
将非std::pair<,>
物品收集到可向后插入的容器中效果很好。将 std::pair<>
项目收集到关联容器也是如此。
但是,将 std::pair<>
项收集到 std::vector<>
中会导致编译器错误:
int main() {
{ // works: non-std::pair<> to back-insertible container
using Item = std::string;
std::vector<Item> input = {"1", "2"};
auto output = Collect<std::vector, Item>::collect(input);
}
{ // works: std::pair<> to associative container
using Item = std::pair<int, std::string>;
std::vector<Item> input = {{1, "1"}, {2, "2"}};
auto output = Collect<std::unordered_map, Item>::collect(input);
}
{ // compiler error: std::pair<> to back-insertible container
using Item = std::pair<int, std::string>;
std::vector<Item> input = {{1, "1"}, {2, "2"}};
auto output = Collect<std::vector, Item>::collect(input);
}
return 0;
}
最后一个案例在 std::vector
中遇到了 static_assert
失败,因为 AssocCollection
概念采用了来自 std::pair<,>
的 [=26] 的键和值类型=],以及 second_type
。如果它这样做并将这两种类型作为 std::vector
的第一个和第二个模板参数,它会为 std::vector<>
的分配器模板参数提供一个无效参数。
这是带有编译器错误的完整示例的 link:https://godbolt.org/z/oY3cfbrYE
我原以为这些概念在这里的行为更像 SFINAE,这样的错误会导致模板特化被排除,而不是导致编译器错误。有没有比回到 SFINAE 更 C++20`ish 的方法来让它工作?
Is there a more C++20`ish way to get this to work, than falling back to SFINAE?
问题在于,当TContainer
对可插入集合进行建模时,Collect
对关联集合的偏特化仍然会实例化约束,这会导致实例化无效,例如std::vector<int,std::string>
触发 static_assert
.
您可以向此偏特化添加额外的约束,以在满足前一个偏特化时阻止其实例化。
/** Collect specialization for associative containers */
template<template<typename...> typename TContainer, typename TItem>
requires (!BackInsertableCollection<TContainer, TItem>) &&
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
is_pair<TItem> && AssocCollection<TContainer, typename TItem::first_type, typename TItem::second_type>
struct Collect<TContainer, TItem> {
static auto collect(std::vector<TItem>& values) {
TContainer<typename TItem::first_type, typename TItem::second_type> container;
for(const auto& value : values) {
container[value.first] = value.second;
}
return container;
}
};