删除未使用的重载会导致编译错误吗?

时间:2016-01-21 18:41:18

标签: c++ templates sfinae overloading argument-deduction

我正在考虑删除一些未使用的重载,并触发编译错误,编译器称这是一个模板替换错误。但我认为“替换失败不是错误”,无论如何,为什么要删除过载导致它?

简单的开头:

#include <string>

int ParseInt(const char *);
int ParseInt(std::string);

bool F(int(*)(const char *));

bool User() {
  return F(ParseInt);
}

这里,User()使用解析例程的地址调用F.一切都很好。 ParseInt已重载,但只有一个重载符合F的签名。

输入F:

的模板化重载
bool F(int(*)(const char *));

template <typename T>
struct MetaDeduce {
  typedef typename T::X type;
};

template <typename T>
typename MetaDeduce<T>::type F(const T&);

现在F有这个奇怪的模板重载,但是没关系,因为函数指针还没有名为X的成员。一切都很好,一切都很好。

... UNTIL

#include <string>

int ParseInt(const char *);
// int ParseInt(std::string);  // commenting this out caused a compiler error!

bool F(int(*)(const char *));

template <typename T>
struct MetaDeduce {
  typedef typename T::X type;
};

template <typename T>
typename MetaDeduce<T>::type F(const T&);

bool User() {
  return F(ParseInt);
}

从godbolt(http://goo.gl/2Yd04p)可以看出,这会产生一个奇怪的编译错误:

10 : error: type 'int (const char *)' cannot be used prior to '::'
     because it has no members
typedef typename T::X type;
^
14 : note: in instantiation of template class 'MetaDeduce<int (const char *)>'
     requested here
typename MetaDeduce<T>::type F(const T&);
^

WTF ???看起来编译器抱怨替换失败,但为什么以前不是问题呢?无论如何,我认为替换失败不是错误!发生了什么事?

1 个答案:

答案 0 :(得分:4)

这是由两种语言属性之间的微妙交互引起的

  1. SFINAE仅适用于直接上下文。您的MetaDeduce<T>未在此类直接上下文中定义,这使typename MetaDeduce<T>::type成为一个严重错误。但SFINAE确实适用于typename T::X,即使注释了ParseInt(std::string)重载,使用它也会编译代码。

    template <typename T>
    typename T::X F(const T&);
    
  2. Live Example(请注意,由于您尚未定义函数,因此会出现链接器错误)

    1. 因此,如果typename MetaDeduct<T>::type是罪魁祸首,为什么它会与ParseInt的两个重载一起使用?好吧,如果你只有模板FParseInt,那么请考虑一下会发生什么。为方便起见,将模板F赋予bool返回类型,以便在替换失败时忽略。

      int ParseInt(const char *);
      int ParseInt(std::string);
      
      template <typename T>
      bool F(T const&);
      
      bool User() { return F(ParseInt); } // error, cannot deduce template argument
      
    2. Live Example

      如果ParseInt有两个重载且调用F(ParseInt)中没有额外信息,则编译器无法推断出ParseInt的哪个版本应与模板参数T匹配。在此代码段的上下文中,这将导致硬错误(因为您将有一个空的重载集),但是由于额外的非模板重载F(int(*)(const char*))它不会(因为ParseInt(const char*)会匹配那个)。请注意,由于参数推导在这里失败,如果返回类型为typename MetaDeduce<T>::Type,则此处甚至不会发生参数替换。

      实际上,ParseInt的两个重载使您免于非直接上下文中的替换失败。一旦你拿走其中一个,参数推断成功,非立即替换失败导致一个硬错误。

      这有点像穿过红灯的街道,并且因为两辆迎面而来的卡车在撞到你之前相互碰撞而得救了。只有一辆卡车,你就会受到打击。