当前位置: 首页 > 知识库问答 >
问题:

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

祁远
2023-03-14

例如,重载这两个:

// (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)

相关问题:概念会取代sfinae吗?

共有1个答案

周超英
2023-03-14

是的概念就是为此目的而设计的。如果发送的参数不满足所需的概念参数,重载解析列表中将不考虑该函数,从而避免了歧义。

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

简单的例子:

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

void print(std::integral auto i) {
    std::cout << "integral: " << i << std::endl;
}
    null
print("hello"); // calls print(auto)
print(7);       // calls print(std::integral auto)

上面的示例演示了编译器如何更喜欢受约束类型(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);
};
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>;
    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)
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>)

不幸的是,C++20不允许概念专门化,否则我们会更进一步:

template<class P>
concept Twople<P, Any, Any> = GenericTwople<P>;

这可以为这个问题增加一个很好的可能的答案,但是概念专门化是不允许的。

1 约束偏序的实际规则更加复杂,请参见:cppreference/C++20规范。

 类似资料:
  • 请考虑以下代码示例: 我确信它不应该编译,因为编译器不应该能够选择两个构造函数中的一个。g-4.7.3显示了这个预期的行为:它说重载构造函数的调用是不明确的。但是,g-4.8.2成功编译了它。 此代码在 C 11 中是否正确,或者它是此版本 g 的错误/功能?

  • 当我运行这段代码时,它会打印。我的问题是为什么没有编译时错误?对象和字符串的默认值为NULL。那么为什么不编译器说。

  • 我希望有一个结构,它接受任意数量的lambdas,并作为所有调用操作符的中心调用点。 如果使用与构造时给出的任何 lambda 不匹配的参数列表调用调用运算符,则应调用默认调用运算符。 我以为下面的代码可以完全做到这一点。每个 lambda 的调用运算符都通过使用“提升”到 类。 当我在结构中没有默认的call操作符时,一切都像预期的那样工作(使用有效的参数列表)。如果我将它添加到结构中(如上面的

  • 本文向大家介绍请解释Java中的概念,什么是构造函数?什么是构造函数重载?什么是复制构造函数?相关面试题,主要包含被问及请解释Java中的概念,什么是构造函数?什么是构造函数重载?什么是复制构造函数?时的应答技巧和注意事项,需要的朋友参考一下 考察点:JAVA构造函数 当新对象被创建的时候,构造函数会被调用。每一个类都有构造函数。在程序员没有给类提供构造函数的情况下,Java编译器会为这个类创建一

  • redux-saga 提供了一些辅助函数,用来在一些特定的 action 被发起到 Store 时派生任务。 这些辅助函数构建在低阶 API 之上。我们将会在高级概念一节看到这些函数是如何实现的。 第一个函数,takeEvery 是最常见的,它提供了类似 redux-thunk 的行为。 让我们演示一下常见的 AJAX 例子。每次点击 Fetch 按钮时,我们发起一个 FETCH_REQUESTE

  • 本文向大家介绍PHP回调函数概念与用法实例分析,包括了PHP回调函数概念与用法实例分析的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了PHP回调函数概念与用法。分享给大家供大家参考,具体如下: 一、回调函数的概念 先看一下C语言里的回调函数:回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数