概念的偏序是如何解决约束重载的?
How is constraint overloading resolved by the partial ordering of concept?
根据 cppreference,包含约束的部分排序用于确定“模板模板参数的最佳匹配”。在示例部分中,“更受约束”的选项,即 stronger/tighter,条件是 selected。但在实践中,当函数概念约束明确排序并且特定调用可以清楚地区分最强选项时,我发现了一些令人困惑的行为。
我正在 gcc 版本 10.2.0(Homebrew GCC 10.2.0)上测试以下内容:使用:
template <typename... Ts>
void foo (Ts... ts) // option 1
{
(cout << ... << ts) << endl;
}
template <typename... Ts> requires (sizeof...(Ts)<=3)
void foo (Ts... ts) // option 2
{
(cout << ... << ts) << endl;
}
调用 foo(1,2)
会 select 选项 2,因为它的约束明显强于选项 1。另一方面,这显然会引起歧义:
template <typename... Ts>
void foo (Ts... ts) // option 1
{
(cout << ... << ts) << endl;
}
template <typename... Ts> requires (sizeof...(Ts)<=3)
void foo (Ts... ts) // option 2
{
(cout << ... << ts) << endl;
}
template <typename... Ts> requires (same_as<Ts, int> && ...)
void foo (Ts... ts) // option 3
{
(cout << ... << ts) << endl;
}
因为foo(1,2)
的调用无法决定是选择选项2还是选项3,因为它们没有可比性。现在,如果我理解正确的话,添加一个连词,例如:
template <typename... Ts>
void foo (Ts... ts) // option 1
{
(cout << ... << ts) << endl;
}
template <typename... Ts> requires (sizeof...(Ts)<=3)
void foo (Ts... ts) // option 2
{
(cout << ... << ts) << endl;
}
template <typename... Ts> requires (same_as<Ts, int> && ...)
void foo (Ts... ts) // option 3
{
(cout << ... << ts) << endl;
}
template <typename... Ts> requires (sizeof...(Ts)<=3) && (same_as<Ts, int> && ...)
void foo (Ts... ts) // option 4
{
(cout << ... << ts) << endl;
}
调用 foo(1,2)
应该使用选项 4 解决,但我的编译器另有说明:
In function 'int main()':
error: call of overloaded 'foo(int, int)' is ambiguous
| foo(1,2);
| ^
note: candidate: 'void foo(Ts ...) [with Ts = {int, int}]'
| void foo (Ts... ts) // option 1
| ^~~
note: candidate: 'void foo(Ts ...) [with Ts = {int, int}]'
| void foo (Ts... ts) // option 2
| ^~~
note: candidate: 'void foo(Ts ...) [with Ts = {int, int}]'
| void foo (Ts... ts) // option 3
| ^~~
note: candidate: 'void foo(Ts ...) [with Ts = {int, int}]'
| void foo (Ts... ts) // option 4
这是为什么?如果这是不可避免的,有什么解决办法吗?
如果你尝试编译:
template <typename... Ts> requires (sizeof...(Ts)<=3)
void foo (Ts... ts) // option 2
{
(cout << ... << ts) << endl;
}
template <typename... Ts> requires (sizeof...(Ts)<=3) && (is_same_v<Ts, int> && ...)
void foo (Ts... ts) // option 4
{
(cout << ... << ts) << endl;
}
使用 clang 12
你收到消息:
note: similar constraint expressions not considered equivalent;
constraint expressions cannot be considered equivalent unless
they originate from the same concept
有了这个提示,您可以将代码重写为:
#include <iostream>
#include <type_traits>
using namespace std;
template <typename... Ts>
void foo (Ts... ts) // option 1
{
(cout << ... << ts) << endl;
}
template<class ... Ts>
concept LessThan3 = (sizeof...(Ts)<=3) ;
template<class ... Ts>
concept AllInt = (std::same_as<Ts, int> && ...);
template <typename... Ts> requires LessThan3<Ts...>
void foo (Ts... ts) // option 2
{
(cout << ... << ts) << endl;
}
template <typename... Ts> requires AllInt<Ts...>
void foo (Ts... ts) // option 3
{
(cout << ... << ts) << endl;
}
template <typename... Ts> requires LessThan3<Ts...> && AllInt<Ts...>
void foo (Ts... ts) // option 4
{
(cout << ... << ts) << endl;
}
int main(){
foo(1,2);
return 0;
}
同时使用 gcc 和 clang 进行编译:demo
当编译器检查一组需求是否比另一组更受限制时,它会递归地扩展概念。它也理解&&
、||
、( )
的意思(所以conjuncts/disjuncts的顺序无所谓,多余的( )
无所谓,等),甚至在概念内部。
这部分非常直观。不直观的是,两个要求在词法上相同不足以被认为是等价的。在扩展概念之前,它们必须是源代码中相同位置的相同表达式。这就要求他们源于同一个概念。
另一个non-intuitive部分是&&
和||
在折叠表达式中失去了它们的特殊含义,所以对于两个折叠表达式被认为是等价的(无论他们使用&&
或 ||
或其他)在扩展概念之前,它们也必须位于源代码中的相同位置。
了解这一点,解决方案是将 sizeof...(Ts) <= 3
和 (same_as<Ts, int> && ...)
抽象为概念。
有很多方法可以做到这一点。您可以根据自己的需要笼统或具体:
-
template <typename ...P>
concept at_most_3 = sizeof...(P) <= 3;
template <typename ...P>
concept all_ints = (std::same_as<P, int> && ...);
用法:requires at_most_3<Ts...> && all_ints<Ts...>
-
template <auto A, auto B>
concept less_eq = A <= B;
template <typename T, typename ...P>
concept all_same_as = (std::same_as<T, P> && ...);
用法:requires less_eq<sizeof...(Ts), 3> && all_same_as<int, Ts...>
即使是完全令人震惊的 template <bool X> concept boolean = X;
,被用作 requires boolean<sizeof...(Ts) <= 3> && boolean<(std::same_as<Ts, int> && ...)>
,似乎也有效!
根据 cppreference,包含约束的部分排序用于确定“模板模板参数的最佳匹配”。在示例部分中,“更受约束”的选项,即 stronger/tighter,条件是 selected。但在实践中,当函数概念约束明确排序并且特定调用可以清楚地区分最强选项时,我发现了一些令人困惑的行为。
我正在 gcc 版本 10.2.0(Homebrew GCC 10.2.0)上测试以下内容:使用:
template <typename... Ts>
void foo (Ts... ts) // option 1
{
(cout << ... << ts) << endl;
}
template <typename... Ts> requires (sizeof...(Ts)<=3)
void foo (Ts... ts) // option 2
{
(cout << ... << ts) << endl;
}
调用 foo(1,2)
会 select 选项 2,因为它的约束明显强于选项 1。另一方面,这显然会引起歧义:
template <typename... Ts>
void foo (Ts... ts) // option 1
{
(cout << ... << ts) << endl;
}
template <typename... Ts> requires (sizeof...(Ts)<=3)
void foo (Ts... ts) // option 2
{
(cout << ... << ts) << endl;
}
template <typename... Ts> requires (same_as<Ts, int> && ...)
void foo (Ts... ts) // option 3
{
(cout << ... << ts) << endl;
}
因为foo(1,2)
的调用无法决定是选择选项2还是选项3,因为它们没有可比性。现在,如果我理解正确的话,添加一个连词,例如:
template <typename... Ts>
void foo (Ts... ts) // option 1
{
(cout << ... << ts) << endl;
}
template <typename... Ts> requires (sizeof...(Ts)<=3)
void foo (Ts... ts) // option 2
{
(cout << ... << ts) << endl;
}
template <typename... Ts> requires (same_as<Ts, int> && ...)
void foo (Ts... ts) // option 3
{
(cout << ... << ts) << endl;
}
template <typename... Ts> requires (sizeof...(Ts)<=3) && (same_as<Ts, int> && ...)
void foo (Ts... ts) // option 4
{
(cout << ... << ts) << endl;
}
调用 foo(1,2)
应该使用选项 4 解决,但我的编译器另有说明:
In function 'int main()':
error: call of overloaded 'foo(int, int)' is ambiguous
| foo(1,2);
| ^
note: candidate: 'void foo(Ts ...) [with Ts = {int, int}]'
| void foo (Ts... ts) // option 1
| ^~~
note: candidate: 'void foo(Ts ...) [with Ts = {int, int}]'
| void foo (Ts... ts) // option 2
| ^~~
note: candidate: 'void foo(Ts ...) [with Ts = {int, int}]'
| void foo (Ts... ts) // option 3
| ^~~
note: candidate: 'void foo(Ts ...) [with Ts = {int, int}]'
| void foo (Ts... ts) // option 4
这是为什么?如果这是不可避免的,有什么解决办法吗?
如果你尝试编译:
template <typename... Ts> requires (sizeof...(Ts)<=3)
void foo (Ts... ts) // option 2
{
(cout << ... << ts) << endl;
}
template <typename... Ts> requires (sizeof...(Ts)<=3) && (is_same_v<Ts, int> && ...)
void foo (Ts... ts) // option 4
{
(cout << ... << ts) << endl;
}
使用 clang 12
你收到消息:
note: similar constraint expressions not considered equivalent;
constraint expressions cannot be considered equivalent unless
they originate from the same concept
有了这个提示,您可以将代码重写为:
#include <iostream>
#include <type_traits>
using namespace std;
template <typename... Ts>
void foo (Ts... ts) // option 1
{
(cout << ... << ts) << endl;
}
template<class ... Ts>
concept LessThan3 = (sizeof...(Ts)<=3) ;
template<class ... Ts>
concept AllInt = (std::same_as<Ts, int> && ...);
template <typename... Ts> requires LessThan3<Ts...>
void foo (Ts... ts) // option 2
{
(cout << ... << ts) << endl;
}
template <typename... Ts> requires AllInt<Ts...>
void foo (Ts... ts) // option 3
{
(cout << ... << ts) << endl;
}
template <typename... Ts> requires LessThan3<Ts...> && AllInt<Ts...>
void foo (Ts... ts) // option 4
{
(cout << ... << ts) << endl;
}
int main(){
foo(1,2);
return 0;
}
同时使用 gcc 和 clang 进行编译:demo
当编译器检查一组需求是否比另一组更受限制时,它会递归地扩展概念。它也理解&&
、||
、( )
的意思(所以conjuncts/disjuncts的顺序无所谓,多余的( )
无所谓,等),甚至在概念内部。
这部分非常直观。不直观的是,两个要求在词法上相同不足以被认为是等价的。在扩展概念之前,它们必须是源代码中相同位置的相同表达式。这就要求他们源于同一个概念。
另一个non-intuitive部分是&&
和||
在折叠表达式中失去了它们的特殊含义,所以对于两个折叠表达式被认为是等价的(无论他们使用&&
或 ||
或其他)在扩展概念之前,它们也必须位于源代码中的相同位置。
了解这一点,解决方案是将 sizeof...(Ts) <= 3
和 (same_as<Ts, int> && ...)
抽象为概念。
有很多方法可以做到这一点。您可以根据自己的需要笼统或具体:
-
template <typename ...P> concept at_most_3 = sizeof...(P) <= 3; template <typename ...P> concept all_ints = (std::same_as<P, int> && ...);
用法:
requires at_most_3<Ts...> && all_ints<Ts...>
-
template <auto A, auto B> concept less_eq = A <= B; template <typename T, typename ...P> concept all_same_as = (std::same_as<T, P> && ...);
用法:
requires less_eq<sizeof...(Ts), 3> && all_same_as<int, Ts...>
即使是完全令人震惊的 template <bool X> concept boolean = X;
,被用作 requires boolean<sizeof...(Ts) <= 3> && boolean<(std::same_as<Ts, int> && ...)>
,似乎也有效!