`typename T::value_type` 应该被定义后面的约束拒绝时编译失败吗?

Should `typename T::value_type` fail to compile when it is meant to be rejected by a constraint that comes later in the definition?

以下代码:

#include <concepts>

template <typename T>
struct Foo
{
  template <std::convertible_to<typename T::value_type> U> requires requires { typename T::value_type; }
  void bar()
  {
  }

  template <typename U>
  void bar()
  {
  }
};

int main()
{
  auto foo = Foo<float> {};
  foo.bar<int>();
}

被 GCC 11 拒绝:

error: ‘float’ is not a class, struct, or union type
     8 |   void bar()
       |        ^~~

我期望发生的是 bar 的第一个定义由于不满足约束而被拒绝,而第二个定义被选中。但是,显然当 GCC 试图用 float 替换 T 时,它在查看约束之前无法编译 typename T::value_type。如果将定义替换为:

,我可以获得预期的行为
template <typename U> requires (requires { typename T::value_type; } && std::convertible_to<U, typename T::value_type>)
void bar()
{
}

我觉得不太优雅。

标准是说第一种方法是非法的还是 GCC 实现中的缺陷?如果是前者,是否有更好的方式来编写此约束(除了定义一个新的命名概念,如 convertible_to_value_type_of)?

编辑: 只是为了根据评论和(现已删除的)答案进行澄清:我理解为什么这段代码会根据 C++20 之前的规则被拒绝.我的意思是,向 C++20 添加概念可能是放宽规则的机会,以便编译器推迟对 typename T::value_type 之类的有效性的验证,直到它检查可能出现的约束在定义的其余部分。我的问题真的是:规则是以这种方式放松的吗?

标准很明确,约束只代入at the point of use or when needed for declaration matching:

The type-constraints and requires-clause of a template specialization or member function are not instantiated along with the specialization or function itself, even for a member function of a local class; substitution into the atomic constraints formed from them is instead performed as specified in [temp.constr.decl] and [temp.constr.atomic] when determining whether the constraints are satisfied or as specified in [temp.constr.decl] when comparing declarations.

这是一个GCC bug。看来 GCC 确实在 requires-clause 中正确处理了这个问题,因此这可以作为一种解决方法:

  template <class U>
      requires std::convertible_to<U, typename T::value_type>
  void bar() 
  {
  }