好的,假设我想检查模板参数是否具有嵌套类型/ typedef XYZ。
template <class T>
struct hasXZY
{
typedef char no;
typedef struct { char x[2]; } yes;
template <class U>
static yes f(typename U::XYZ*);
template <class /*U*/>
static no f(...);
enum {value = sizeof(f<T>(0))==sizeof(yes)};
};
按预期正常工作。
现在考虑一下:
template <class T>
struct hasXZY
{
typedef char no;
typedef struct { char x[2]; } yes;
static yes f(typename T::XYZ*);
static no f(...);
enum {value = sizeof(f(0))==sizeof(yes)};
};
hasXYZ<int>
现在导致编译时错误。好的,f不是模板功能。但另一方面,当hasXYZ
通过hasXYZ<int>::value
实例化为int时,编译器可以轻松地从候选列表中排除f(int::XYZ*)
。我只是不明白为什么在类模板中实例化成员函数声明失败必须使整个类实例化失败。有什么想法吗?
编辑: 我的问题是:为什么成员函数声明应该都是格式正确的?由于编译器仅在使用它们时实例化方法,为什么它需要正确的声明。考虑上面的示例2作为此功能的可能用例。
答案 0 :(得分:4)
仅在为函数重载决策创建候选集时使用SFINAE。在您的第一个示例中,您正在调用重载的f()函数,并且由于SFINAE而排除了第一个函数。
在第二个示例中,当实例化hasXZY时,必须很好地定义其所有成员,并且模板参数的替换不得失败。它适用于int :: XYZ。
由于替换失败,会员不会被排除在课堂之外。
答案 1 :(得分:3)
我不是C ++语言律师,但我会对此有所了解。
在第二个示例中,必须明确定义成员函数,因为{{1>}实例化hasXZY
后,它们不再是模板函数。为了说服自己,用手代替int
“:
T
并观察到编译失败,编译错误与以前相同(至少在GCC中):
struct hasXYZ
{
typedef int T;
typedef char no;
typedef struct { char x[2]; } yes;
static yes f(T::XYZ*);
static no f(...);
enum {value = sizeof(f(0))==sizeof(yes)};
};
int main()
{
std::cout << hasXYZ::value << "\n";
}
相比之下,第一个示例在手动实例化后编译并按预期运行; foo.cc:9: error: ‘T’ is not a class or namespace
成员仍在f
上模板化。
答案 2 :(得分:2)
编辑:我的问题是:为什么成员函数声明应该都是格式良好的?由于编译器仅在使用它们时实例化方法,为什么它需要正确的声明。考虑上面的示例2作为此功能的可能用例。
当隐式实例化类模板特化时,编译器必须检查该成员的完整声明符,因为它需要知道有关声明的基本信息。这样可以有助于类模板特化的大小。
如果检查声明部分会发现它声明了一个数据成员,那么该类的sizeof
值可能会产生不同的值。如果您已经声明了一个函数指针,那么就是这种情况
yes (*f)(typename T::XYZ*);
C ++语言的定义方式是只有在解析整个声明后才能知道声明的类型。
你可以说你把静态放在那里,因此在这种情况下,不需要计算它的大小。但是名称查找需要 才能知道名称hasXZY<T>::f
所指的内容,并且根本没有声明名称 f 。编译器将不实例化hasXYZ::f
的定义,但它将仅实例化声明的非定义部分,以获取其类型并添加其名称用于名称查找目的的类类型。我认为支持在可能有效的特定情况下声明名称的延迟实例化会使C ++编译器和C ++规范的实现更加复杂,因为没有可比的好处。
最后,在您尝试调用它的示例中,编译器具有来实例化声明,因为它需要查找名称f
,为此它需要来了解该声明是否是函数或其他内容。所以我甚至在理论上甚至看不到你的例子可以在没有实例化声明的情况下工作的方式。请注意,在任何情况下,这些不实例化函数的定义。