gpt4 book ai didi

c++ - 使用函数重载解析的概念(而不是 SFINAE)

转载 作者:行者123 更新时间:2023-12-01 15:10:55 25 4
gpt4 key购买 nike

试图告别 SFINAE。

是否可以使用 concepts来区分函数,这样编译器就可以根据发送的参数是否满足concept来匹配正确的函数约束?

例如,重载这两个:

// (a)
void doSomething(auto t) { /* */ }

// (b)
void doSomething(ConceptA auto t) { /* */ }

因此,当被调用时,编译器会在每次调用时匹配正确的函数:
doSomething(param_doesnt_adhere_to_ConceptA); // calls (a)
doSomething(param_adheres_to_ConceptA); // calls (b)

相关问题: Will Concepts replace SFINAE?

最佳答案

concepts是为此目的而设计的。如果发送的参数不符合所需的概念参数,则该函数不会被考虑在重载解析列表中,从而避免歧义。

此外,如果发送的参数满足多个功能,则会选择更具体的一个。

简单示例:

void print(auto t) {
std::cout << t << std::endl;
}

void print(std::integral auto i) {
std::cout << "integral: " << i << std::endl;
}

以上 print函数是可以一起存在的有效重载。
  • 如果我们发送非整数类型,它将选择第一个
  • 如果我们发送一个整数类型,它会更喜欢第二个

  • 例如,调用函数:
    print("hello"); // calls print(auto)
    print(7); // calls print(std::integral auto)

    没有歧义 ——这两个功能可以完美地并排在一起。

    无需任何 SFINAE 代码 ,例如 enable_if - 它已经应用(很好地隐藏)。

    在两个概念之间进行选择

    上面的例子展示了编译器如何更喜欢约束类型(std::integral auto)而不是无约束类型(只是 auto)。但这些规则也适用于两个相互竞争的概念。如果一个更具体,编译器应该选择更具体的一个。当然,如果这两个概念都得到满足并且没有一个更具体,这将导致歧义。

    那么,是什么让一个概念更加具体呢?如果它基于另一个1。

    通用概念 - GenericTwople :
    template<class P>
    concept GenericTwople = requires(P p) {
    requires std::tuple_size<P>::value == 2;
    std::get<0>(p);
    std::get<1>(p);
    };

    更具体的概念 - Twople:
    class Any;

    template<class Me, class TestAgainst>
    concept type_matches =
    std::same_as<TestAgainst, Any> ||
    std::same_as<Me, TestAgainst> ||
    std::derived_from<Me, TestAgainst>;

    template<class P, class First, class Second>
    concept Twople =
    GenericTwople<P> && // <= note this line
    type_matches<std::tuple_element_t<0, P>, First> &&
    type_matches<std::tuple_element_t<1, P>, Second>;

    请注意,Twople 需要满足 GenericTwople 要求,因此它更具体。

    如果您在我们的 Twople 中替换该行:
        GenericTwople<P> && // <= note this line

    根据此行带来的实际需求,Twople 仍将具有相同的需求,但不再比 GenericTwople 更具体。当然,这与代码重用是我们更喜欢基于 GenericTwople 定义 Twople 的原因。

    现在我们可以玩各种重载:
    void print(auto t) {
    cout << t << endl;
    }

    void print(const GenericTwople auto& p) {
    cout << "GenericTwople: " << std::get<0>(p) << ", " << std::get<1>(p) << endl;
    }

    void print(const Twople<int, int> auto& p) {
    cout << "{int, int}: " << std::get<0>(p) << ", " << std::get<1>(p) << endl;
    }

    并调用它:
    print(std::tuple{1, 2});        // goes to print(Twople<int, int>)
    print(std::tuple{1, "two"}); // goes to print(GenericTwople)
    print(std::pair{"three", 4}); // goes to print(GenericTwople)
    print(std::array{5, 6}); // goes to print(Twople<int, int>)
    print("hello"); // goes to print(auto)

    我们可以更进一步,因为上面介绍的 Twople 概念也适用于多态:
    struct A{
    virtual ~A() = default;
    virtual std::ostream& print(std::ostream& out = std::cout) const {
    return out << "A";
    }
    friend std::ostream& operator<<(std::ostream& out, const A& a) {
    return a.print(out);
    }
    };

    struct B: A{
    std::ostream& print(std::ostream& out = std::cout) const override {
    return out << "B";
    }
    };

    添加以下重载:
    void print(const Twople<A, A> auto& p) {
    cout << "{A, A}: " << std::get<0>(p) << ", " << std::get<1>(p) << endl;
    }

    并调用它(而所有其他重载仍然存在):
        print(std::pair{B{}, A{}}); // calls the specific print(Twople<A, A>)

    代码: https://godbolt.org/z/3-O1Gz

    不幸的是,C++20 不允许概念专门化,否则我们会走得更远,使用:
    template<class P>
    concept Twople<P, Any, Any> = GenericTwople<P>;

    这可以为 this SO question 添加一个不错的可能答案,但是不允许概念特化。

    1 约束部分排序的实际规则更复杂,参见: cppreference/ C++20 spec .

    关于c++ - 使用函数重载解析的概念(而不是 SFINAE),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60414373/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com