静态重载与 SFINAE 结合使用

Overloading-on-static in conjunction with SFINAE

我在 Visual Studio 2013 年尝试编译以下程序并遇到 C2686: cannot overload static and non-static member functions 错误。

#include <iostream>
#include <type_traits>

struct foo
{
  template<bool P>
  static std::enable_if_t<P> bar()
  {
    std::cerr << "P is true\n";
  }

  template<bool P>
  std::enable_if_t<!P> bar()
  {
    std::cerr << "P is false\n";
  }
};

int main()
{
  foo f;
  f.bar<true>();
}

我很熟悉这个编译器错误 — 请参阅 this Whosebug answer,但我很惊讶地看到这个错误与 SFINAE 一起使用,编译器将始终丢弃重载集中的两个重载之一。

Visual Studio 2013 是否正确遵循此处的标准,或者是否可以与 SFINAE 一起在 static 上重载?

编辑:将上面的示例与 return 类型

上的重载进行对比

没有 SFINAE,您不能在 static 上重载,也不能在 return 类型上重载。但是,Visual Studio 2013 支持与 SFINAE 一起重载 return 类型。

以下程序与上面的程序相同,但删除了 static 并更改了第二个 foo::bar 声明的 return 类型,编译正确。

#include <iostream>
#include <type_traits>

struct foo
{
  template<bool P>
  std::enable_if_t<P> bar()
  {
    std::cerr << "P is true\n";
  }

  template<bool P>
  std::enable_if_t<!P, int> bar()
  {
    std::cerr << "P is false\n";
    return 42;
  }
};

int main()
{
  foo f;
  f.bar<true>();
}

在我看来Visual Studio 2013年这两个案例中有一个是错误的,但我希望语言律师能够提供一个明确的答案。

令人惊讶的是,MSVC 是正确的。 (我知道,震惊。)[over.load]/p1-2:

1 Not all function declarations can be overloaded. Those that cannot be overloaded are specified here. A program is ill-formed if it contains two such non-overloadable declarations in the same scope. [Note: This restriction applies to explicit declarations in a scope, and between such declarations and declarations made through a using-declaration (7.3.3). It does not apply to sets of functions fabricated as a result of name lookup (e.g., because of using-directives) or overload resolution (e.g., for operator functions). —end note]

2 Certain function declarations cannot be overloaded:

  • Function declarations that differ only in the return type cannot be overloaded.
  • Member function declarations with the same name and the same parameter-type-list cannot be overloaded if any of them is a static member function declaration (9.4). Likewise, member function template declarations with the same name, the same parameter-type-list, and the same template parameter lists cannot be overloaded if any of them is a static member function template declaration. [...]
  • [...]

两个bar()声明具有相同的名称,相同的参数类型列表,以及相同的模板参数列表,其中至少有一个是static,因此不能重载。

背景

我认为 Visual Studio 在这种情况下是不正确的。

[1] 指出编译函数调用分两步进行,名称查找,然后是重载解析,如果需要(我们将在这种情况下看到没有必要)。

名称查找生成候选函数列表。名称查找由两个步骤组成,Argument-dependent lookup,然后是模板参数推导。

如果名称查找生成多个可能的函数调用,则会进行重载解析以确定要调用的正确函数。

SFINAE 是一种元编程技术,用于在模板参数推导的子步骤中操纵候选函数集 [2]。 SFINAE 在模板参数替换期间引入替换错误,这会阻止将所述函数添加到候选集 [3]。

例子

让我们手动编译这个例子。

  1. 编译器需要解析

    的函数调用

    f.bar();

  2. 首先,进行名称查找

2.1。参数依赖查找 运行s。由于没有参数,此步骤不会减少候选列表。

2.2。模板参数推导 运行s.

2.2.1。模板参数替换 运行s。这会将 P 值代入每个 enable_if_t<> 表达式。 enable_if_t<> 在其谓词表达式(此处为 P)为假时生成替换失败。因此,从候选列表中删除 P 导致替换失败的函数。模板参数替换后,此代码只能产生 1 个候选函数,因为 enable_if_t<> 表达式是互斥的。

结论

看来 Visual Studio 在模板参数替换步骤完成之前正在检查重载规则。如果它有 运行 模板参数替换,那么重载决策将永远不会发生,因为候选列表包含一个函数。

参考资料