Discussing with another C++ programmer, we came out with this piece of code:

#include <iostream>

template <class... Args>
void foo(int, Args...) {
    std::cout << "1";
}

template <class...Args>
void foo(Args..., int) {
    std::cout << "2";
}

int main() {
    foo(11);                        // A
    foo<>(11);                      // B
    foo(11, 33);                    // C
    foo<int>(11, 33);               // D
    foo(11, 22, 33);                // E
    foo<int, int>(11, 22, 33);      // F
}

I was pretty convinced that all the calls to foo were ambiguous, so I decided to test it with different compilers (g++ 5.3.0, clang 3.7.0 and Visual C++ 2015Upd1).

They all agreed I was wrong. Statement A, B and C are interpreted as non ambiguous, and picked the first overload.

Problem is: here ends the agreement, because on cases D, E and F they all behave differently.

In g++ 5.3.0 the first overload is always selected, so the program compiles as is and prints 111111.

In clang 3.7.0 D and F are considered ambiguous. Why not B? No clue. Anyway, they’re commented out in the code on coliru, so the program actually prints 1111.

Visual Studio 2015 (and Update 1)  behaves like clang 3.7.0, but when commenting D and F, the compiler goes crazy and gives an internal compiler error on statement E:

1>------ Build started: Project: test, Configuration: Debug Win32 ------
1> main.cpp
1>d:\projects\spikes\test\test\main.cpp(18): fatal error C1001: An internal error has occurred in the compiler.
1> (compiler file 'f:\dd\vctools\compiler\cxxfe\sl\p1\cxx\dymto.c', line 6771)
1> To work around this problem, try simplifying or changing the program near the locations listed above.
1> Please choose the Technical Support command on the Visual C++
1> Help menu, or open the Technical Support help file for more information
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

I didn’t had time yet to verify what the standard says about the ambiguity of these statements, but, ambiguity or not, I would expect case B to behave as cases D and F.

This code reveals at least one bug in VS2015Upd1 (I sent them feedback few minutes ago), but I wonder if the difference between g++ and clang highlights a gray area in the standard, or if one of the two is wrong (I’m keen to consider clang correct, partly because it usually contains less bugs, but mostly because it gives me partial reason in considering that an ambiguity 🙂 ).

I’ll keep this post open, and update it with new considerations as they arise. Feel free to comment with your opinions, or your knowledge of the C++ standard.

Update 1: Fixed an embarrassing number of typos in the post.

5 Comments

  1. Andrey Upadyshev

    For the B: call like `foo<>(…);`means to call a template function `foo`, it’s does not mean an explicit specifying a zero length parameters pack. It looks like the current standard has no way to express an explicit instantiation of a function with zero length parameters pack.

    Reply
    • Marco Foco

      This is interesting, and it explains why B is not interpreted as D and F.
      At first sight it might look like a hole in the standard, but I also couldn’t come up with a possible use-case for an empty parameter pack: you typically want to include something more in the parameter pack, since an empty parameter pack is always a better match.

      Reply
  2. Andrey Upadyshev

    Oh, there is some markup parser which “fixes” my code. I meant a call of foo with angle brackets of course.

    Reply
    • Marco Foco

      I fixed your previous comment 🙂

      Reply
      • Andrey Upadyshev

        Thank you, Marco!

        Reply

Leave a Reply