Overloading-on-static in conjunction with SFINAE

474 views Asked by At

I tried compiling the following program in Visual Studio 2013 and got the C2686: cannot overload static and non-static member functions error.

#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>();
}

I'm familiar with this compiler error—see this StackOverflow answer, but was surprised to see the error in conjunction with SFINAE, whereby the compiler will always discard one of the two overloads from the overload set.

Is Visual Studio 2013 correctly following the standard here, or should it be possible to overload on static in conjunction with SFINAE?

EDIT: Contrast example above with overloading on return type

Without SFINAE, you can't overload on static, and you also can't overload on return type. However, Visual Studio 2013 supports overloading on return type in conjunction with SFINAE.

The following program, which is the same as the program above but removes static and changes the return type for the second foo::bar declaration, compiles correctly.

#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>();
}

It seems to me Visual Studio 2013 is getting one of these two cases wrong, but I'm hoping a language lawyer can provide a definite answer.

2

There are 2 answers

5
T.C. On BEST ANSWER

Surprisingly enough, MSVC is correct. (I know, shock.) [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. [...]
  • [...]

The two bar() declarations have the same name, same parameter-type-list, and same template parameter list, at least one of which is static, and therefore cannot be overloaded.

1
Jeremy Wright On

Background

I think Visual Studio is incorrect in this case.

[1] states compiling a function call happens in two steps, name lookup, followed by overload resolution, if necessary (which we will see in this case is not necessary).

Name lookup generates a list of candidate functions. Name lookup is composed of two steps, Argument-dependent lookup, followed by template argument deduction.

If name lookup generates multiple possible function calls, overload resolution occurs to determine the correct function to call.

SFINAE is a meta-programming technique to manipulate the candidate function set during a sub-step of template argument deduction [2]. SFINAE induces a substitution error during template argument substitution which prevents adding said function to the candidate set [3].

Example

Let us manually compile this example.

  1. The compiler needs to resolve the function call for

    f.bar();

  2. First, name lookup occurs

2.1. Argument Dependent lookup runs. Since there are no arguments this step doesn't reduce the list of candidates.

2.2. Template argument deduction runs.

2.2.1. Template argument substitution runs. This substitutes the P value into each enable_if_t<> expression. enable_if_t<> generates a substitution failure when it's predicate expression (here P) is false. Hence, functions where P induced a substitution failure are removed from the candidate list. After template argument substitution this code can only result in 1 candidate function since the enable_if_t<> expressions are mutually exclusive.

Conclusion

It appears that Visual Studio is checking overload rules before the template argument substitution step completes. If it had run template argument substitution, then overload resolution would never occur since the candidate list contains a single function.

References