Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Which is the more specialized template function? clang and g++ differ on that

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.

like image 573
Amir Kirsh Avatar asked Jun 20 '16 14:06

Amir Kirsh


2 Answers

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.

like image 176
W.F. Avatar answered Nov 15 '22 13:11

W.F.


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.

like image 21
Richard Hodges Avatar answered Nov 15 '22 14:11

Richard Hodges