使用函数重载决议的概念(而不是 SFINAE)
Using concepts for function overload resolution (instead of SFINAE)
试图与 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)
相关问题:
是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)而不是无约束类型(just auto) .但这些规则也适用于两个相互竞争的概念。如果一个更具体,编译器应该选择更具体的一个。当然,如果这两个概念都满足并且 none 更具体,这将导致歧义。
嗯,是什么让概念更具体?如果是基于另一个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>;
这可以为 添加一个很好的可能答案,但是不允许概念专业化。
1 约束的部分排序的实际规则更复杂,参见:cppreference / C++20 spec .
试图与 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)
相关问题:
是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)而不是无约束类型(just auto) .但这些规则也适用于两个相互竞争的概念。如果一个更具体,编译器应该选择更具体的一个。当然,如果这两个概念都满足并且 none 更具体,这将导致歧义。
嗯,是什么让概念更具体?如果是基于另一个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>;
这可以为
1 约束的部分排序的实际规则更复杂,参见:cppreference / C++20 spec .