哪个是更专业的模板功能? clang和g ++有所不同

时间:2016-06-20 14:14:14

标签: c++ templates c++11 variadic-templates overloading

在使用可变参数模板时,在this SO question之后(注意:并不是强制要求遵循这个问题),我对clang(3.8)和g ++(6.1)的不同行为表示如下模板重载函数:

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;
}

代码:http://coliru.stacked-crooked.com/a/b62fa93ea88fa25b

输出

|---|-----------------------------------------------------------------------------|
| # |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>()                     |             |             |             |
|---|-----------------------------------------------------------------------------|

最后两个案例(4和5)存在问题:我对更专业的模板的期望是错误的吗?如果是这样,谁是正确的4,clang或g ++? (请注意,代码编译时两者都没有任何错误或警告,但结果不同)。

我自己试图回答这个问题,我曾多次通过规范(14.5.6.2 Partial ordering of function templates)cppreference中的“更专业”规则 - 似乎更专业的规则应该给出结果我'我期待(如果没有,可能会出现歧义错误,但事实并非如此)。那么,我在这里错过了什么?

等待(1):请不要急于带上Herb Sutter和他prefer not to overload templates的“template methods quiz”。这些肯定很重要,但语言仍然允许模板重载! (这确实是一个强化点,为什么你不想重载模板 - 在某些边缘情况下,它可能会混淆两个不同的编译器,或者使程序员感到困惑。但问题不在于是否使用它,它是: 如果您使用它,那么正确的行为是什么? )。

等待(2):请不要急于提出其他可能的解决方案。肯定有。以下是两个:one with inner structanother with inner static methods。两者都是合适的解决方案,都按预期工作,但有关上述模板重载行为的问题仍然存在。

2 个答案:

答案 0 :(得分:4)

正如Holt所说,当涉及到可变参数模板参数推导时,标准是非常严格的:

  

14.8.2.5/9

     

如果P的形式包含T或i,那么每个参数Pi都是   将相应的模板参数列表P与相应的进行比较   A的相应模板参数列表的参数Ai。如果是   P的模板参数列表包含不是的包扩展   最后一个模板参数,整个模板参数列表是一个   非推断的上下文。如果Pi是一个包扩展,那么Pi的模式   与模板参数列表中的每个剩余参数进行比较   每个比较推导出后续的模板参数   由Pi扩展的模板参数包中的位置

由T.C.解释。意味着Ts1...可以从第二个参数中推断出来,但它没有留下Ts2...推论的空间。因此,明显的铿锵声就在这里,gcc就错了......只有在第二个参数包含完全相同的模板参数时才能选择,例如:

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

仍然是示例5.不满足此要求,并且不允许编译器选择重载。

答案 1 :(得分:2)

仅供参考:不是答案。这是对评论中的问题的回答:

在gcc5.3上进行以下小改动会导致它产生预期的结果,或至少与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:~$

并且为了记录,修改程序以评估推断的上下文中的所有包在两个平台上都取得了成功:

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

归功于W.F.引导我走向这个方向。