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

Rum*_*rak 4 c++ language-lawyer c++-concepts c++20

如果所有模板参数都是唯一的,我正在尝试编写一个返回 true 的函数。

伪代码:

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

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
}
Run Code Online (Sandbox Code Playgroud)

gcc 和 clang 都同意这无法编译,因为unique.cpp:7:8: error: duplicate base type ‘X<int>’ invalid.

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

将模板参数替换为模板化实体声明中使用的 requires 表达式可能会导致在其需求中形成无效类型或表达式,或者违反这些需求的语义约束。在这种情况下, requires 表达式的计算结果为 false 并且不会导致程序格式错误。

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

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

Bar*_*rry 6

哪些类型的替换失败不允许出现在 requires 表达式中?

和其他地方一样。这实际上并不是特定于概念的。这是您示例的稍微修改版本,它演示了 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>()));
Run Code Online (Sandbox Code Playgroud)

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

如果替换导致无效的类型或表达式,则类型推导失败。无效类型或表达式是一种格式错误的类型,如果使用替换参数编写,则需要诊断。[ 注意:如果不需要诊断,程序仍然是格式错误的。访问检查是作为替换过程的一部分完成的。— end note ]只有函数类型、其模板参数类型及其显式说明符的直接上下文中的无效类型和表达式才会导致推导失败。[注意:类型和表达式的替换可能会导致诸如类模板特化和/或函数模板特化的实例化、隐式定义函数的生成等效果。这些效果不在“立即上下文”中,并且可以导致程序格式错误。— 尾注 ]

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


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