以下C ++代码无法在MS Visual Studio 2010中编译:
class Foo
{
public:
/// Provides the signature of the methods that can be given to addValueSetListener
template <typename TT>
struct ChangeHandler
{
typedef void ( TT::* OnSetValueMethod )();
};
template <typename TT>
void bar_ok(TT*, void ( TT::* )(), bool = false) {}
template <typename TT>
void bar_ok(const char*, TT*, void ( TT::* )()) {}
template <typename TT>
void bar_fails(TT*, typename ChangeHandler<TT>::OnSetValueMethod, bool = false) {}
template <typename TT>
void bar_fails(const char*, TT*, typename ChangeHandler<TT>::OnSetValueMethod) {}
void testBar() {}
};
int main()
{
Foo foo;
foo.bar_ok ("allo",& foo, & Foo::testBar); // compiles
foo.bar_fails("allo",& foo, & Foo::testBar); // compile ERROR
}
对于ERROR行,编译器错误为'TT': must be a class or namespace when followed by '::'
。
失败的行与没有行的唯一区别是bar_fails声明&#34;方法指针类型&#34;参数void (TT::*)()
通过&#34;模板化typedef&#34;,而bar_ok直接声明它。
请注意,如果没有const char*
的重载,模板化的typedef工作正常。在const char * overload可用的情况下,编译器错误地选择了bar_fails的TT=[const char]
重载,但它正确地选择了bar_ok的TT = Foo重载。当typedef用于&#34;简单&#34;时,不会出现此问题。数据如TT *或浮动*。
答案 0 :(得分:2)
原因是在bar_ok
情况下,SFINAE可以应用,因为错误的构造const char::*
出现在模板参数替换的直接上下文中。在bar_fail
的情况下,它被删除了一步(隐藏在“template typedef”中),这意味着SFINAE不再适用,编译器必须处理const char::*
的句法废话,从而停止并报告错误
换句话说,并不是编译器在bar_fail
中选择了错误的重载。它必须在两种情况下检查两种重载,但在第一种情况下,它允许忽略SFINAE的错误,而在第二种情况下,它“太晚了”。
答案 1 :(得分:1)
Angew提供了一个简单的解释,说明为什么我的OP中的代码不起作用。那么有一个解决方法吗?事实证明这很简单,但解释并不适合评论,所以现在就是这样。
正如Angew所指出的,SFINAE仅适用于直接替代级别,因此编译:
template <typename TT> void testFunc(TT) {}
template <typename TT> void testFunc(typename TT::Foo) {}
int main()
{
testFunc<int>(3);
}
确实编译器知道丢弃testFunc
的第二个重载:一个整数没有嵌套的Foo
类型。如果SFINAE在C ++中根本不可用,这将停止编译。
现在,如果您稍微更改上述实现以使用类似traits的类,则以下完全等效的代码将不再编译:
template <typename TT>
struct Helper
{
typedef typename TT::Foo MyFoo;
};
template <typename TT> void testFunc(TT) {}
template <typename TT> void testFunc(typename Helper<TT>::MyFoo) {}
int main()
{
testFunc<int>(3);
}
因为编译器是&#34;内部&#34;解析MyFoo typedef时的Helper<int>
类;它在int::Foo
bal,直接SFINAE不适用,所以它放弃编译。
您可能会注意到,在我的OP中,我没有明确指定模板参数,而在上面,我这样做:那是因为编译器知道param是一个int所以它匹配所有{{以testFunc
为参数的1}},它不会尝试所有int
。在我的OP中,我没有明确指定模板参数来获取错误,因为第一个函数参数为我做了这个。让我们取消方法指针,因为它解决了问题,我原来的问题是由以下更简单的代码展示的,我没有明确指定模板参数:
testFunc<int>
编译器将struct Foo
{
template <typename TT> struct Helper
{
typedef typename TT::Foo MyFoo;
};
template <typename TT> void bar(TT*, typename Helper<TT>::MyFoo) {}
template <typename TT> void bar(const char*, TT*) {}
};
int main()
{
Foo foo;
foo.bar("allo", & foo); // ok
}
的第一个函数调用参数视为foo.bar
,因此它知道它必须同时考虑const char*
和bar(const char*, Foo*)
。然后在bar(const char*, Helper<const char>::MyFoo)
内部尝试解析Helper<const char>
并停止编译(再次直接SFINAE不适用)。
一种解决方案是删除const char::Foo
并坚持使用直接嵌套类型:
Helper
然而,这对我来说并不理想,因为在我的实际代码中,我的struct Foo
{
template <typename TT> void bar(TT*, typename TT::Foo) {}
template <typename TT> void bar(const char*, TT*) {}
};
期待一个方法指针,并且我试图在声明中明确这一点(尽管我现在想知道是否模板化的typedef有助于或阻碍,但这是另一回事)。
第二种解决方案是使用SFINAE使编译器在bar()
级别进行阻止。事实证明这很容易做到:为bar
定义缺少部分的Helper
专门化:
const char
现在,当编译器将struct Foo
{
template <typename TT> struct Helper
{
typedef typename TT::Foo MyFoo;
};
template <> struct Helper<const char>
{
};
template <typename TT> void bar(TT*, typename Helper<TT>::MyFoo) {}
template <typename TT> void bar(const char*, TT*) {}
};
与TT
匹配时,它有两个需要考虑的重载:
const char
但void bar(const char*, Helper<const char>::MyFoo)
void bar(const char*, Foo*)
在专业化中并不存在,因此可以使用SFINAE:编译器不必进入&#34;内部&#34; Helper<const char>::MyFoo
。
这3条专业化线足以解决问题。