std::is_invocable 的奇怪行为

ple*_*ndo 2 c++ templates type-traits language-lawyer c++17

我在以下程序中遇到 std::is_invocable 问题:

#include <iostream>
#include <type_traits>

void g() {}

template<bool B, typename T>
void f(T t) {
    if constexpr (B)
        g(t);
}

int main() {
    std::cerr << std::boolalpha <<
        std::is_invocable_v<decltype(&f<true, int>), int> << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

我原以为程序输出为假,因为 f<true, int> 无法实例化。但是,GCC(从 10.0.1 20200224 开始)不会编译,错误消息为

test.cpp: In instantiation of 'void f(T) [with bool B = true; T = int]':
test.cpp:14:51:   required from here
test.cpp:9:10: error: too many arguments to function 'void g()'
    9 |         g(t);
      |         ~^~~
test.cpp:4:6: note: declared here
    4 | void g() {}
      |  
Run Code Online (Sandbox Code Playgroud)

和 Clang(从 11.0.0 开始)甚至打印true

在这种情况下,正确的行为是什么?

Bar*_*rry 10

问题归结为:decltype(&f<true, int>)实际上实例化了f<true, int>吗?

如果是,则是病态的程序,因为生病,形成实例(不能调用gint,如要求)。

如果不是,那么正确答案invocable_vtrue- 因为函数体上没有 SFINAE,并且函数的签名明确允许此调用。

似乎 gcc 认为是(和硬错误),而 clang 认为不是(并产生了true)。我认为clang在这里是正确的。我们的规则是:

[temp.inst]/10 :

如果以涉及重载解析的方式使用函数模板或成员函数模板特化,则隐式实例化特化声明 ([temp.over])。

[temp.inst]/11

实现不应隐式实例化函数模板、变量模板、成员模板、非虚拟成员函数、成员类、类模板的静态数据成员或 constexpr if 语句的子语句 ([stmt. if]),除非需要这样的实例化。

我们在这里确实涉及重载解析(函数地址),但这只是实例化函数模板的声明。我认为不需要实例化函数模板的定义f返回void,而不是类似的东西auto),所以 gcc 在这里太急切地实例化它。

也就是说,它当然不应该 yield false