2

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.

Community
  • 1
  • 1
Craig M. Brandenburg
  • 3,354
  • 5
  • 25
  • 37
  • Are you sure Visual C++ 2013 *has* `std::enable_if_t

    `? Try writing that as `typename std::enable_if

    ::type`. As I recall 2013 has some C++11 support (extensive for the library), but no C++14. Oh, that stands to reason, when one thinks about the **years**!

    – Cheers and hth. - Alf Jun 26 '15 at 00:28
  • 1
    it compiled successfully in gcc and clang http://coliru.stacked-crooked.com/a/65a5417d9e24bcde – Steephen Jun 26 '15 at 00:30
  • @Cheers Yes, VC2013 supports `std::enable_if_t`. My program above compiles correctly by removing the `static` from the first overload declaration. – Craig M. Brandenburg Jun 26 '15 at 00:31
  • @CraigM.Brandenburg: Thanks! Learned something new today too then. :) – Cheers and hth. - Alf Jun 26 '15 at 00:36

2 Answers2

6

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.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • I've edited my question to contrast the `static` case with overloading on return type, which, without SFINAE, is also prohibited by the standard, but, unlike the `static` case, compiles OK when using SFINAE. Because of this difference, I feel the paragraph of the standard you've quoted is insufficient for explaining what's going on. Also, it doesn't explain why, as @Steephen pointed out in a comment above, why gcc and clang support the `static` case. – Craig M. Brandenburg Jun 26 '15 at 00:48
  • @CraigM.Brandenburg Overloading on return type is fine for function templates because bullet 2.1 (which I just added to the answer) outlaws only functions with different return types and not function templates (whose signature includes the return type - see [defns.signature.templ] and [defns.signature.member.templ]). As to the behavior of GCC and Clang, it's either bugs or intentional nonconformance. Bullet 2.2 unambiguously prohibits the overloading at issue here. – T.C. Jun 26 '15 at 21:22
  • Thanks for clarifying your answer. The key issue here is whether SFINAE precludes both function definitions from being in the same overload set. If so then the overload rules you cite may not apply. So the question is: How does SFINAE relate to overload rules? – Craig M. Brandenburg Jun 26 '15 at 22:15
  • @CraigM.Brandenburg They are somewhat orthogonal things. The rules in [over.load] does not apply to the building of overload sets; they render ill-formed any program that "contains two such non-overloadable declarations in the same scope", regardless of whether actual ambiguity will result from the overload set constructed for any given call. – T.C. Jun 26 '15 at 22:18
  • Interesting. So a prohibited use of overloading remains prohibited even if the overload set for all lookups for those functions must necessarily contain at most one member. – Craig M. Brandenburg Jun 26 '15 at 22:32
1

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

Jeremy Wright
  • 200
  • 1
  • 6
  • 1
    As soon as you have the declarations, the program is ill-formed due to the rules in [over.load]. What would happen during a hypothetical call to that function is beside the point. – T.C. Jun 26 '15 at 21:19