当使用无效表达式时,概念是否应该编译失败?

Should concepts fail to compile when an invalid expression is used?

我正在尝试实现一种紧凑的方法来检测编译时是否有可用函数(我使用 std::max 作为示例)。我想到了这个:

#include <stdio.h>  
#include <algorithm>           // (1)

namespace std { struct max; }  // (2)

template<typename A>
concept bool have_std_max = requires(A const& a1, A const& a2) {
  { std::max(a1, a2) }
};

template <typename A> 
constexpr A const &my_max(A const &a1, A const &a2) {
  if constexpr(have_std_max<A>) {
    return std::max(a1, a2);
  }
  else {
    return (a1 > a2) ? a1 : a2;
  }
}

int main() {
  int x = 5, y = 6;
  return my_max(x, y);
}

如果我注释掉 (1),则检测有效并且我的代码使用 constexpr else 分支(参见 in Compiler Explorer)。但是,如果我同时注释掉 (1) 和 (2),这段代码将无法编译,因为名称 std::max 对编译器来说是未知的。在这种情况下,这个概念不应该只是 return false 吗?有没有一种方法可以实现类似的东西而不必声明一个虚拟 max?

C++的模板系统从来不是编写错误代码的借口。模板只为所谓的 dependent 类型和表达式提供余地。 (一个很长的主题,需要更深入地研究 in this answer。)出于我们的目的,std::max 形式的限定名称不涉及任何依赖项,因此它 必须 在它出现的地方是正确的。反过来,这意味着查找必须成功。

您尝试添加 max 声明是在正确的轨道上。通过这样做,非依赖限定名总能成功地找到一个声明。同时,整个表达式仍然是依赖的(由于涉及 a0a1)。剩下的就是避免污染 std 命名空间,我们不允许这样做:

#include <algorithm>

namespace dirty_tricks {

namespace max_fallback {

// non-deducible on purpose
template<typename Dummy>
void max() = delete;

} // max_fallback

namespace lookup {

// order of directives is not significant
using namespace std;
using namespace max_fallback;

} // lookup

} // dirty_tricks

template<typename Val>
concept bool have_std_max = requires(Val arg) {
    // N.B. qualified call to avoid ADL
    dirty_tricks::lookup::max(arg, arg);
};

(通过删除 <algorithm> 测试代码时,请确保仍然声明了 std 命名空间,否则 using 指令可能会失败。如本 Coliru demo 所示。)

现在 dirty_tricks::lookup::max 要么同时找到 std::maxdirty_tricks::max_fallback::max,要么只找到后者,但它不会失败。我们还确保我们自己的 max 重载不能通过删除它来产生有效的表达式(否则有效的调用看起来会非常不同)。