重载C++模板化函数

Overloading of C++ templated functions

本文关键字:函数 C++ 重载      更新时间:2023-10-16

我认为以下代码应该可以工作,但是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函数被认为是等效的事实造成的。虽然我认为第二个比第三个更适合(就像第一个一样)。

不过我不确定。谁能告诉我是我错了还是编译器错了?

TLDR;程序编译失败的原因是,在重载解析期间,第二个和第三个重载是同样良好的匹配。特别是,两者都不比另一个更专业。由于重载分辨率无法选择最佳匹配项,因此程序格式不正确。治愈的方法就是用你的方式摆脱它。

问题所在

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 作为参数模板和 模板-2 作为参数模板,并再次使用转换后的 模板-2 作为参数模板,模板-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;
}

活生生的例子

在第二个重载中,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;
}

您应该用一些标记调度替换重载。

编写一个函数,然后检查第二个 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 类型的第三个参数,为您提供编译时分支。 对最后一个类型再次重复,您就完成了。