19

While playing with variadic templates, following this SO question (note: it is not mandatory to go there for following this question), I came to a different behavior of clang (3.8) and g++ (6.1) for the following template overloaded functions:

template <class... Ts>
struct pack { };

template <class a, class b>
constexpr bool starts_with(a, b) {
    return false;
}

template <template <typename...> class PACK_A,
          template <typename...> class PACK_B, typename... Ts1, typename... Ts2>
constexpr bool starts_with(PACK_A<Ts1..., Ts2...>, PACK_B<Ts1...>) {
    return true;
}

int main() {
   std::cout << std::boolalpha;
   std::cout << starts_with(pack<int, float, double>(),
                            pack<float, int, double>())        << std::endl;
   std::cout << starts_with(pack<int, float, double>(),
                            pack<int, float, double, int>())   << std::endl;
   std::cout << starts_with(pack<int, float, double>(),
                            pack<int, float, int>())           << std::endl;
   std::cout << starts_with(pack<int, float, double>(),
                            pack<int, float, double>())        << std::endl;
   std::cout << starts_with(pack<int, float, double>(),
                            pack<int>())                       << std::endl;
}

Code: http://coliru.stacked-crooked.com/a/b62fa93ea88fa25b

Output

|---|-----------------------------------------------------------------------------|
| # |starts_with(a, b)                  | expected    | clang (3.8) | g++ (6.1)   |
|---|-----------------------------------|-------------|-------------|-------------|
| 1 |a: pack<int, float, double>()      |  false      |  false      |  false      |
|   |b: pack<float, int, double>()      |             |             |             |
|---|-----------------------------------|-------------|-------------|--------- ---|
| 2 |a: pack<int, float, double>()      |  false      |  false      |  false      |
|   |b: pack<int, float, double, int>() |             |             |             |
|---|-----------------------------------|-------------|-------------|--------- ---|
| 3 |a: pack<int, float, double>()      |  false      |  false      |  false      |
|   |b: pack<int, float, int>()         |             |             |             |
|---|-----------------------------------|-------------|-------------|--------- ---|
| 4 |a: pack<int, float, double>()      |  true       |  true       |  false      |
|   |b: pack<int, float, double>()      |             |             |             |
|---|-----------------------------------|-------------|-------------|--------- ---|
| 5 |a: pack<int, float, double>()      |  true       |  false      |  false      |
|   |b: pack<int>()                     |             |             |             |
|---|-----------------------------------------------------------------------------|

The last two cases (4 and 5) are in question: are my expectation for the more specialized template wrong? and if so, who is right in case 4, clang or g++? (note that the code compiles without any error or warning on both, yet with different results).

Trying to answer that myself, I went several times through the "more specialized" rules in the spec (14.5.6.2 Partial ordering of function templates) and in cppreference -- it seems that the more specialized rule shall give the result I'm expecting (one may expect ambiguity error if not, but this is not the case either). So, what am I missing here?


Wait (1): please don't rush and bring the "prefer not to overload templates" of Herb Sutter and his template methods quiz. These are surely important, but the language still allows templates overloading! (It is indeed a strengthening point why you should prefer not to overload templates -- in some edge cases it may confuse two different compilers, or confuse the programmer. But the question is not whether to use it or not, it is: what is the right behavior if you do use it?).

Wait (2): please don't rush to bring other possible solutions. There are for sure. Here are two: one with inner struct and another with inner static methods. Both are suitable solutions, both work as expected, yet the question regarding the above template overloading behavior still stays.

Community
  • 1
  • 1
Amir Kirsh
  • 12,564
  • 41
  • 74
  • 1
    I suspect that the result is due to `PACK_A`. Compiler should not be able to deduce the first parameter pack `Ts1...` as it is not at the end of the template. Therefore it should get empty and as such the overload should never be chosen. I don't know how come clang have chosen it in case 4. – W.F. Jun 20 '16 at 14:29
  • 2
    As @W.F. says, the standard tell us that *"If the template argument list of P contains a pack expansion that is not the last template argument, the entire template argument list is a non-deduced context."* (14.8.2.5/8), so g++ seems to be right and clang wrong. – Holt Jun 20 '16 at 14:30
  • 1
    confirming, changing the signature to `template constexpr bool starts_with(pack, pack)` fixes it in gcc. – Richard Hodges Jun 20 '16 at 14:35
  • @RichardHodges you mean in clang?... – W.F. Jun 20 '16 at 14:38
  • @W.F. no. it causes gcc to give the expected answer. – Richard Hodges Jun 20 '16 at 14:42
  • @RichardHodges in this case in my opinion both clang and gcc are wrong :) – W.F. Jun 20 '16 at 14:45
  • @W.F. note that in two very similar cases (a sequence of two packs), g++ and clang succeed in deducing the first pack, resulting with the expected result (though, in one case with a warning in clang): http://coliru.stacked-crooked.com/a/f93c81352d526dc6 and http://coliru.stacked-crooked.com/a/52b7d44cc4482c24 -- so why these are ok and the above is not? – Amir Kirsh Jun 20 '16 at 14:51
  • 1
    @AmirKirsh the `Ts...` from your examples are not deduced. They are known at the time the templated struct is specialized. This is the most crucial difference here. As the `Ts...` is not deduced there it can get placed there exactly as is. – W.F. Jun 20 '16 at 14:56
  • @W.F. again confirmed. information answer updated. you should post an answer so that we can +1 it and delete mine. – Richard Hodges Jun 20 '16 at 14:59
  • 2
    What? You *should* overload function templates. You should not *specialise* them. – n. m. could be an AI Jun 20 '16 at 15:55

2 Answers2

4

As Holt mentioned the standard is very strict when it comes to variadic template parameters deduction:

14.8.2.5/9

If P has a form that contains T or i, then each argument Pi of the respective template argument list P is compared with the corresponding argument Ai of the corresponding template argument list of A. If the template argument list of P contains a pack expansion that is not the last template argument, the entire template argument list is a non-deduced context. If Pi is a pack expansion, then the pattern of Pi is compared with each remaining argument in the template argument list of A. Each comparison deduces template arguments for subsequent positions in the template parameter packs expanded by Pi.

This as interpreted by T.C. would mean that Ts1... can be deduced from the second argument but it leaves no room to Ts2... deduction. As such apparently clang would be the right here and gcc would be wrong... The overload should be therefor chosen only if the second parameter would contain exactly the same template parameters e.g:

starts_with(pack<int, float, double>(), pack<int, float, double>())

Still example 5. does not fulfil this requirement and does not allow compiler to chose the overload.

W.F.
  • 13,888
  • 2
  • 34
  • 81
  • 1
    `PACK_A` is a non-deduced context, but that doesn't mean that `Ts1...` is deduced as empty. It means it is not deduced from the first argument, but it can be deduced from the second argument. `Ts2...`, OTOH, is not deduced at all and is thus deduced to be empty. – T.C. Jun 20 '16 at 17:10
  • @T.C. so `starts_with(pack(), pack())` -> `true` is actually expected behaviour? – W.F. Jun 20 '16 at 17:34
  • 1
    Sort of. It breaks http://eel.is/c++draft/temp.param#11, but nobody actually diagnoses that. – T.C. Jun 20 '16 at 20:30
  • @T.C. 14.8.2.5/9 treat `PACK_A` as "the entire template argument list is a non-deduced context" - fair enough. But, none-deduced context can be deduced by another deduced context. So let's follow to 14.1/11: "A template parameter pack of a function template shall not be followed by another template parameter **unless that template parameter can be deduced from the parameter-type-list of the function**..." -- I believe we fall in the case of "parameter can be deduced from the parameter-type-list" (`PACK_B`) -- which means cases 4 and 5 can be deduced and shall work as expected (true, true)! – Amir Kirsh Jun 21 '16 at 14:28
  • 1
    @T.C. Well, I think I found the rule in the spec... 14.8.2.1/1: "When a function parameter pack appears in a non-deduced context ..., **the type of that parameter pack is never deduced**." But then, clang is wrong and g++ is right in the initial scenario, and both are wrong in Richard Hodges scenario. A none deduced context shall fail this template (SFINAE) resulting with the 'false' result. Isn't it? – Amir Kirsh Jun 21 '16 at 14:44
  • 1
    `Ts1` is a template parameter pack, not a function parameter pack. A function parameter pack is the `p` in `template void f(Ts... p);`. – T.C. Jun 21 '16 at 15:08
2

information only: not an answer. This is a response to a question in the comments:

on gcc5.3 making the following small change induces it to produce the expected results, or at least the same results as clang.

rhodges@dingbat:~$ cat nod.cpp
#include <iostream>

using namespace std;

template <class... Ts>
struct pack { };

template <class a, class b>
constexpr bool starts_with(a, b) {
    return false;
}

template <typename... Ts1, typename... Ts2 >
constexpr bool starts_with(pack<Ts1..., Ts2...>, pack<Ts1...>) {
    return true;
}

int main() {
   std::cout << std::boolalpha;
   std::cout << starts_with(pack<int, float, double>(), pack<float, int, double>()) << std::endl;
   std::cout << starts_with(pack<int, float, double>(), pack<int, float, double, int>()) << std::endl;
   std::cout << starts_with(pack<int, float, double>(), pack<int, float, int>()) << std::endl;
   std::cout << starts_with(pack<int, float, double>(), pack<int, float, double>()) << std::endl;
   std::cout << starts_with(pack<int, float, double>(), pack<int>()) << std::endl;
}


rhodges@dingbat:~$ g++ -std=c++14 nod.cpp && ./a.out
false
false
false
true
false
rhodges@dingbat:~$ g++ --version
g++ (Ubuntu 5.3.1-14ubuntu2.1) 5.3.1 20160413
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

rhodges@dingbat:~$

and for the record, modifying the program to evaluate all packs in deduced contexts brings success on both platforms:

rhodges@dingbat:~$ cat nod.cpp
#include <iostream>

using namespace std;

template <class... Ts>
struct pack { };

template <class a, class b>
constexpr bool starts_with_impl(a, b) {
    return false;
}

template<typename...LRest>
constexpr bool starts_with_impl(pack<LRest...>, pack<>)
{
    return true;
}

template<typename First, typename...LRest, typename...RRest>
constexpr bool starts_with_impl(pack<First, LRest...>, pack<First, RRest...>)
{
    return starts_with_impl(pack<LRest...>(), pack<RRest...>());
}

template <typename... Ts1, typename... Ts2 >
constexpr bool starts_with(pack<Ts2...> p1, pack<Ts1...> p2) {
    return starts_with_impl(p1, p2);
}

int main() {
    std::cout << std::boolalpha;
    std::cout << starts_with(pack<int, float, double>(), pack<float, int, double>()) << std::endl;
    std::cout << starts_with(pack<int, float, double>(), pack<int, float, double, int>()) << std::endl;
    std::cout << starts_with(pack<int, float, double>(), pack<int, float, int>()) << std::endl;
    std::cout << starts_with(pack<int, float, double>(), pack<int, float, double>()) << std::endl;
    std::cout << starts_with(pack<int, float, double>(), pack<int>()) << std::endl;
}


rhodges@dingbat:~$ g++ -std=c++14 nod.cpp && ./a.out
false
false
false
true
true

Credit to W.F. for guiding me in this direction.

Richard Hodges
  • 68,278
  • 7
  • 90
  • 142