【问题标题】:Ignoring template instantiation failures in C++20 concept specializations忽略 C++20 概念特化中的模板实例化失败
【发布时间】:2022-01-18 05:53:40
【问题描述】:

假设我想编写一个类Collect,它为我提供了将给定std::vector<> 的项目传输到任何类型的集合的功能。 所有项目类型都支持转移到可反向插入的集合,但仅转移到关联集合(unordered_map、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 类,首先定义它的空模板表单:

/** 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 {};

然后我使用上面定义的概念创建这个类的特化:

/** 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&lt;,&gt; 项目收集到可反向插入的容器中可以正常工作。将std::pair&lt;&gt; 项目收集到关联容器也是如此。 但是,将 std::pair&lt;&gt; 项收集到 std::vector&lt;&gt; 会导致编译器错误:

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&lt;,&gt;first_typesecond_type 中获取键和值类型。如果它这样做并将这两种类型作为第一个和第二个模板参数放置到std::vector,它会为std::vector&lt;&gt; 的分配器模板参数提供一个无效参数。 以下是包含编译器错误的完整示例的链接:https://godbolt.org/z/oY3cfbrYE

我本来希望这些概念在这里表现得更像 SFINAE,这样的错误会导致模板专业化被排除,而不是导致编译器错误。有没有比回退到 SFINAE 更 C++20`ish 的方式来让它工作?

【问题讨论】:

  • SFINAE 不会抑制来自std::vector&lt;int,std::string&gt;::value_type 的错误。

标签: c++ templates c++20 c++-concepts static-assert


【解决方案1】:

有没有比回退更多的 C++20`ish 方法来让它工作? 去 SFINAE?

问题是当TContainer建模回可插入集合时,Collect对关联集合的部分特化仍然会实例化约束,从而导致std::vector&lt;int,std::string&gt;等无效实例化触发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;
    }
};

Demo.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-05-14
    • 2021-10-06
    • 1970-01-01
    • 1970-01-01
    • 2021-02-17
    • 2021-12-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多