requires 子句中不允许哪些替换失败?

Which substitution failures are not allowed in requires clauses?

我正在尝试编写一个 returns 正确的函数,前提是所有模板参数都是唯一的。

伪代码:

template<typename... Ts>
auto types_are_unique() -> bool {
  return (no two Ts are the same);
}

我不想 "manually" 比较每个 T ,而是想利用这样一个事实,即如果两个或多个基 类 相同,则不允许多重继承。

#include <utility>

template <typename T>
struct X {};

template <typename... Ts>
struct Test : X<Ts>... {};

template <typename... Ts>
constexpr auto types_are_unique() -> bool {
  return false;
}

template <typename... Ts>
requires requires { Test<Ts...>{}; }
constexpr auto types_are_unique() -> bool {
  return true;
}

int main() {
  static_assert(types_are_unique<int, float>()); // compiles
  static_assert(not types_are_unique<int, int>()); // fails
}

gcc 和 clang 都认为编译失败是因为 unique.cpp:7:8: error: duplicate base type ‘X<int>’ invalid.

这令人惊讶,正在阅读 https://en.cppreference.com/w/cpp/language/constraints#Requires_expressions

The substitution of template arguments into a requires-expression used in a declaration of a templated entity may result in the formation of invalid types or expressions in its requirements, or the violation of semantic constraints of those requirements. In such cases, the requires-expression evaluates to false and does not cause the program to be ill-formed.

clang和gcc错了吗?还是 cppreference 错误?或者(很可能)我读错了吗?

哪些 类 的替换失败不允许出现在 requires 表达式中?请参阅当前 C++20 草案的相应部分。

Which classes of substitution failures are not allowed to occur in requires expressions?

和其他地方一样。这实际上并不特定于概念。这是您的示例的稍微修改的版本,它演示了 C++11 的相同问题:

using size_t = decltype(sizeof(0));

template <typename T>
struct X {};

template <typename... Ts>
struct Test : X<Ts>... {};

template <typename...> struct typelist { };

template <typename... T, size_t = sizeof(Test<T...>)>
constexpr auto types_are_unique(typelist<T...>) -> bool {
    return true;
}
constexpr auto types_are_unique(...) -> bool {
    return false;
}

// ok
static_assert(types_are_unique(typelist<int, float>()));

// compile error
static_assert(not types_are_unique(typelist<int, int>()));

问题与替换的直接上下文中的内容和不内容有关。这是一个在 [temp.deduct]/8 中引入但并未真正彻底定义的术语:

If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is one that would be ill-formed, with a diagnostic required, if written using the substituted arguments. [ Note: If no diagnostic is required, the program is still ill-formed. Access checking is done as part of the substitution process. — end note ] Only invalid types and expressions in the immediate context of the function type, its template parameter types, and its explicit-specifier can result in a deduction failure. [ Note: The substitution into types and expressions can result in effects such as the instantiation of class template specializations and/or function template specializations, the generation of implicitly-defined functions, etc. Such effects are not in the “immediate context” and can result in the program being ill-formed. — end note ]

一般的想法是,只有声明中的失败才会导致替换失败,而定义中的失败会导致硬错误导致程序格式错误。在此处的示例中,问题不在于 Test<int, int> 的声明,而在于它的定义(在我们到达其基数 类 的地方)。这被认为为时已晚 - 直接上下文之外的失败不再是替代失败。


我们甚至有一个核心问题(1844)要求更好的定义。