【问题标题】:Overloading of C++ templated functionsC++ 模板函数的重载
【发布时间】:2013-10-14 17:13:18
【问题描述】:

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

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

【问题讨论】:

  • 第二个重载意味着什么?它与第三个重载完全一样,除了只针对最后一个元素...
  • 第二个重载只能匹配元组的last元素,第三个匹配元组的任意元素。

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


【解决方案1】:

TLDR; 程序编译失败的原因是第二个和第三个重载在重载解析过程中同样匹配。特别是,两者都不比另一个更专业。因为重载决议不能选择最佳匹配,所以程序是非良构的。解决方法是让 SFINAE 摆脱困境。

问题

14.5.6.2 函数模板的部分排序[temp.func.order]

2 偏序选择两个函数模板中的哪个更多 通过依次转换每个模板来比另一个专门化(参见 下一段)并使用 函数类型。扣除过程确定是否其中一项 模板比其他模板更专业。如果是这样,越 专用模板是由偏序选择的模板 过程。

3 为每种类型生成转换后的模板, 非类型,或模板模板参数(包括模板参数 packs (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 中描述。 — 尾注] 扣除过程使用 转换后的类型作为参数模板和原始类型 另一个模板作为参数模板。这个过程完成 偏序比较中涉及的每种类型两次:一次 使用转换后的 template-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.

【讨论】:

  • 如果你说的是正确的,它们的功能1也不应该被选中,因为可以应用完全相同的推理(即Tuple=Arg1)。我还认为,给定函数 3 的参数实例,你不能调用函数 2,它会变得更加专业。
  • @PierreBdR 谢谢,我更正了答案。 F1 比 F3 更特化,因为 0 比 N 更特化,但 F2 和 F3 由于未推断上下文而同样特化。
  • 好吧,这就是我错过的规则!非常感谢!
  • 很高兴能帮上忙,由于参数推导导致重载解析失败的事实有点令人费解(即 F1/F2/F3 参数推导成功为他们自己 参数,但不是在重载解决期间玩交叉演绎游戏时)
【解决方案2】:

在第二个重载中,std::tuple_size&lt;Tuple&gt;::value-1 部分取决于模板参数Tuple,因此不是更好的匹配,或者用 C++ 来说,“更专业”。这就是为什么它被认为与显式具有N 的第三个重载相同。

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


如果你想解决你的问题,你可以禁用第三个重载,因为它会匹配第二个:

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

【讨论】:

  • 我必须说我很怀疑。但是,您知道标准的哪一段与这里相关吗?
  • @PierreBdR 13.3.3 最佳可行函数 [over.match.best] p1,最后一个项目符号:- F1 和 F2 是函数模板特化,F1 的函数模板根据 14.5.6.2 中描述的部分排序规则,它比 F2 的模板更专业。如果 B 的所有推导参数都适用于 A,则后一段定义 A 比 B“更专业”,但反之则不然。
  • @TemplateRex 那不是说第二个函数比第三个函数更专业吗?
  • @DanielFrey 如果删除第三个重载,那么第二个调用将不起作用!
  • @PierreBdR 我添加了自己的答案,因为解释占用的空间比预期的要多。
【解决方案3】:

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

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

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

里面可能有一些decayremove_const

这个想法是std::is_same&lt;X,Y&gt;true_type 如果它们相同,则false_type 否则。 helper 重载 true 和 false 类型的第三个参数,为您提供编译时分支。对最后一种类型再次重复,您就完成了。

【讨论】:

  • 是的,我知道标签技术。但我希望能够删除它们;)
猜你喜欢
  • 2010-09-18
  • 1970-01-01
  • 1970-01-01
  • 2012-09-02
  • 2016-10-03
  • 2019-04-13
  • 2014-04-20
  • 2013-06-11
  • 1970-01-01
相关资源
最近更新 更多