重载C ++模板化函数

时间:2013-10-14 17:13:19

标签: c++ templates c++11 overloading overload-resolution

我认为以下代码应该正常工作,但g ++和clang ++都返回完全相同的错误(尽管Visual C ++ 2012没有)。

#include <iostream>
#include <tuple>

template <int N, typename T>
struct A { };

template <typename Tuple>
double result(const Tuple& t, const A<0, typename std::tuple_element<0, Tuple>::type>& a)
{
  return 0;
}

template <typename Tuple>
double result(const Tuple& t, const A<std::tuple_size<Tuple>::value-1,
                                      typename std::tuple_element<std::tuple_size<Tuple>::value-1,Tuple>::type>& a)
{
  return 1;
}

template <typename Tuple, int N>
double result(const Tuple& t, const A<N, typename std::tuple_element<N, Tuple>::type>& a)
{
  return 0.5;
}

int main()
{
  auto a = std::make_tuple(0, 1, 2., 3., 4);
  std::cout << result(a, A<0,int>()) << std::endl;
  std::cout << result(a, A<2,double>()) << std::endl;
  std::cout << result(a, A<4,int>()) << std::endl; // Fails if uncommented
  return 0;
}

错误是由于最后一行以及第二个和第三个result函数被认为是等效的。虽然我认为第二个比第三个更合适(就像第一个一样)。

我不确定。任何人都可以告诉我,如果我错了或编译器是什么?

3 个答案:

答案 0 :(得分:6)

在第二次重载中,std::tuple_size<Tuple>::value-1部分取决于模板参数Tuple,因此不是更好的匹配,或者在C ++中说“更专业”。这就是为什么它被认为是与第三个明确具有N

相同的重载

只有您的第一次重载使用0的常量值,该值不依赖于Tuple,因此是更好的匹配。


如果你想解决问题,你可以禁用第三次重载,以便它与第二次重叠时匹配:

template <typename Tuple, int N>
typename std::enable_if< N != std::tuple_size<Tuple>::value-1, double >::type
result(const Tuple& t, const A<N, typename std::tuple_element<N, Tuple>::type>& a)
{
  return 0.5;
}

答案 1 :(得分:6)

TLDR; 您的程序无法编译的原因是第二次和第三次重载在重载解析期间同样匹配良好。特别是,两者都不比另一个更专业。由于重载决策无法选择最佳匹配,因此程序格式错误。解决问题的方法是SFINAE。

问题

14.5.6.2功能模板的部分排序[temp.func.order]

  

2部分排序选择两个功能模板中的哪一个更多   通过依次转换每个模板来实现专业化(参见   下一段)并使用。执行模板参数推导   功能类型。扣除过程确定是否其中之一   模板比另一个更专业。如果是这样,就越多   专用模板是由部分排序选择的模板   处理。

     

3为每种类型生成转换后的模板   非类型或模板模板参数(包括模板参数   其包(14.5.3))合成独特的类型,价值或类别   分别使用模板并将其替换为每次出现   模板的函数类型中的参数。

对于所有三个重载,第一个合成参数是相等的,并且因为所有参数都是逐个考虑的,所以我们可以关注第二个。

您的第一个重载转换为以下合成的第二个参数

const A<0, typename std::tuple_element<0, Arg1>::type>&

您的第二个重载转换为以下合成的第二个参数

const A<
        std::tuple_size<Arg1>::value-1, typename        
        std::tuple_element<std::tuple_size<Arg1>::value-1, Arg1>::type
>&

您的第三个重载转换为以下合成的第二个参数

const A<Arg2, typename std::tuple_element<Arg2, Arg1>::type>&    

14.8.2.4在部分排序期间推导模板参数[temp.deduct.partial]

  

2两组类型用于确定偏序。对于   涉及的每个模板都有原始的函数类型和   转换后的函数类型。 [注:改造后的创作   类型在14.5.6.2中描述。 - 尾注]扣除过程使用   转换后的类型作为参数模板和原始类型   另一个模板作为参数模板。这个过程完成了   对于部分排序比较中涉及的每种类型两次:一次   使用转换的模板-1作为参数模板和   template-2作为参数模板,再次使用转换后的模板   template-2作为参数模板,template-1作为参数   模板。

很明显,第一个和第二个重载没有第二个模板参数来推断,因此它们至少与第三个重载一样专用。问题是第三个是否可以从第一个和第二个重载'合成的第二个参数推导出它的N参数。

对于第一次重载,N=0就是这样,因此第一次重载比第三次过载更专业。这就是你的第一个函数调用选择第一个重载的原因。

对于第三次重载,不会发生参数推导,它是一个非推断的上下文:

14.8.2.5从类型[temp.deduct.type]中扣除模板参数

  

5未推断的上下文是:

     

- ...

     

- 非类型模板参数或子表达式引用模板参数的数组。

     

- ...

这意味着第三次重载也至少与第二次重载一样。因此,重载决策无法选择一个,并且程序格式不正确。

治愈

只需在enable_if内使用非重叠条件进行两次重载(使用SFINAE)。在这种情况下,这会绕过重载决策。

template <typename Tuple, int N>
typename std::enable_if<N == std::tuple_size<Tuple>::value-1, double>::type
result(const Tuple& t, const A<N, typename std::tuple_element<N, Tuple>::type>& a)
{
  return 1;
}

template <typename Tuple, int N>
typename std::enable_if<N != std::tuple_size<Tuple>::value-1, double>::type
result(const Tuple& t, const A<N, typename std::tuple_element<N, Tuple>::type>& a)
{
  return 0.5;
}

Live Example

答案 2 :(得分:3)

你应该用一些标签调度替换你的重载。

编写一个函数,然后以静态方式检查第二个arg A is_same是否为元组中的第一个类型,调用具有依赖于该类型的类型的另一个函数。在假分支上重复最后一次。

 helper( t, a, std::is_same<A, std::tuple_element<0, Tuple>>() );

可能有一些decayremove_const

我们的想法是,std::is_same<X,Y>如果相同则为true_type,否则为false_typehelper重载true和false类型的第三个参数,为您提供编译时分支。再次重复最后一个类型,你就完成了。