如何实现与参数顺序无关的 std::same_as 的广义形式(即超过两种类型的参数)?

How to implement the generalized form of std::same_as (i.e. for more than two type parameters) that is agnostic to parameter order?

背景

我们知道std::same_as这个概念与顺序无关(换句话说,对称):std::same_as<T, U>等价于std::same_as<U, T>)。在这个问题中,我想实现一些更通用的东西:template <typename ... Types> concept same_are = ... 检查包 Types 中的类型是否彼此相等。

我的尝试

#include <type_traits>
#include <iostream>
#include <concepts>

template <typename T, typename... Others>
concept same_with_others = (... && std::same_as<T, Others>);

template <typename... Types>
concept are_same = (... && same_with_others<Types, Types...>);

template< class T, class U> requires are_same<T, U>
void foo(T a, U b) {
    std::cout << "Not integral" << std::endl;
}

// Note the order <U, T> is intentional
template< class T, class U> requires (are_same<U, T> && std::integral<T>)
void foo(T a, U b) {
    std::cout << "Integral" << std::endl;
}

int main() {
    foo(1, 2);
    return 0;
}

(我的目的是枚举包中所有可能的有序类型对)

不幸的是,这段代码 would not compile,编译器抱怨对 foo(int, int) 的调用不明确。我相信它认为 are_same<U, T>are_same<T, U> 不等同。我想知道为什么代码失败我该如何修复它(以便编译器将它们视为等同的)?

来自cppreference.com Constraint_normalization

The normal form of any other expression E is the atomic constraint whose expression is E and whose parameter mapping is the identity mapping. This includes all fold expressions, even those folding over the && or || operators.

所以

template <typename... Types>
concept are_same = (... && same_with_others<Types, Types...>);

是"atomic"。

所以 are_same<U, T>are_same<T, U> 确实不等价。

我不知道如何实现它:-(

问题是,这个概念:

template <typename T, typename... Others>
concept are_same = (... && std::same_as<T, Others>);

这个概念的规范化形式是……就是这样。我们不能"unfold"这个(没办法),而且现在的规则没有通过"parts"一个概念来规范化。

换句话说,你需要做的是让你的概念正常化为:

... && (same-as-impl<T, U> && same-as-impl<U, T>)

进入:

... && (is_same_v<T, U> && is_same_v<U, T>)

并考虑一个折叠表达式 && 约束包含另一个折叠表达式约束 && 如果其基础约束包含另一个的基础约束。如果我们有那个规则,那将使您的示例有效。

将来可能会添加这个 - 但围绕包含规则的问题是我们不想要求编译器全力以赴并实施完整的 SAT 求解器来检查约束包含。这个似乎并没有让它变得那么复杂(我们实际上只是通过折叠表达式添加 &&|| 规则),但我真的不知道。

但是请注意,即使我们有这种折叠表达式包含,are_same<T, U> 仍然不会包含 std::same_as<T, U>。它只会包含 are_same<U, T>。我不确定这是否可能。

churill 是对的。使用 std::conjunction_v 可能会有帮助。

   template <typename T,typename... Types>
   concept are_same = std::conjunction_v<std::same_as<T,Types>...>;