调用模板函数时出现歧义

时间:2017-01-27 20:59:08

标签: c++ templates c++14 variadic-templates ambiguity

我有以下问题:

template< typename T >
class A
{};

class B
{
  public:
    template< typename... T >
    void operator()( A<T>... a )
    {
      std::cout << "A<T>\n";
    }

    template< typename callable >
    void operator()( callable f )
    {
      std::cout << "callable\n";
    }
};


int main()
{
  B      b;
  A<int> a;

  b( a );
}

调用b( a )不明确 - 我期望输出A<T>,即执行operator()的第一个定义。有谁知道如何解决它?

2 个答案:

答案 0 :(得分:9)

直到并包括标准工作草案N4606(这包括已发布的C ++ 11和C ++ 14标准),调用确实含糊不清,并且Clang拒绝它是正确的。最新草案N4618中引入的更改使部分排序规则选择A<T>...重载。这是一个非常近期的变化;我们需要给编译器一些时间来实现它。

MSVC 2015 U3和EDG 4.11选择A<T>...重载,因此它们之前不符合要求,并且在这方面神奇地符合最新草案。

在模板参数推导之后,我们有两个重载,它们都是模板特化,并且基于转换(两者的标识转换,显然)同样好,所以重载解析必须求助于函数模板的部分排序。 / p>

该过程在[temp.deduct.partial]的标准中描述,我们对第8段感兴趣。在N4618草案之前,它说:

  

如果A是从函数参数包转换而P不是a   参数包,类型扣除失败。否则,使用结果   类型PA,然后按照中所述完成扣除   14.8.2.5。如果P是函数参数包,则将参数模板的每个剩余参数类型的类型A与   键入函数参数包的 declarator-id P。每   比较推导出后续位置的模板参数   模板参数包由函数参数包扩展。如果   对于给定类型,演绎中的类型,演绎成功   模板被认为至少与来自的类型一样专业   参数模板。

(强调我的上下)

尝试从第一次重载到第二次重载时,A是一个包而P不是,所以上段中的第一句适用;扣除失败。反过来尝试推论,第三句和第四句适用,但是扣除再次失败,因为我们试图从一般形式A<T>的参数中推导出callable形式的参数的参数。

因此,演绎双向失败;两种模板都不比另一种更专业;这个电话很模糊。

该段的新措辞如下:

  

使用生成的类型PA,然后将扣除完成为   在14.8.2.5中描述。如果P是函数参数包,则为类型   参数模板的每个剩余参数类型的A是   与函数的 declarator-id 的类型P进行比较   参数包。每个比较推导出模板参数   模板参数包中的后续位置由扩展的   功能参数包。 同样,如果A从a转换而来   功能参数包,与每个剩余参数进行比较   参数模板的类型。如果给定的扣除成功   类型,参数模板中的类型至少被认为是   与参数模板中的类型一样专门。

请注意,第一句话已经消失,并被强调的句子取代,这允许从包A中扣除到非包装P

现在,由于新规则,扣除成功从A<T>升级到callable,但仍然无法相反(没有任何改变)。这使得第一次过载变得更加专业化。

快速修复:您可以向第一个重载添加一个前导非包装参数:

template<class T, class... Ts> void operator()(A<T>, A<Ts>...)

这将避免功能参数列表中第一个位置的包和非包装之间的比较。对于所有编译器,非包A<T>显然比callable更专业。

如果你需要一个匹配没有参数的调用的重载,请单独提供一个(抱歉......)。

答案 1 :(得分:3)

谢谢。

我解决了以下问题:

#include <iostream>

template< typename T >
class A
{};

class B
{
  public:
  template< typename... T >
  void operator()( A<T>... a )
  {
  std::cout << "A<T>\n";
  }

  template< typename T >
  void operator()( A<T> a )
  {
  std::cout << "A<T>\n";
  }

  template< typename callable >
  void operator()( callable f )
  {
  std::cout << "callable\n";
  }
};


int main()
  {
    B      b;
    A<int> a;

    b( a );
    b( );
}

这允许在没有参数的情况下调用operator()