当前位置: 动力学知识库 > 问答 > 编程问答 >

c++ - 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.

网友答案:

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.

网友答案:

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.

[email protected]:~$ 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;
}


[email protected]:~$ g++ -std=c++14 nod.cpp && ./a.out
false
false
false
true
false
[email protected]:~$ 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.

[email protected]:~$

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

[email protected]:~$ 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;
}


[email protected]:~$ g++ -std=c++14 nod.cpp && ./a.out
false
false
false
true
true

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

分享给朋友:
您可能感兴趣的文章:
随机阅读: