2013-08-20 64 views
1

以下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 
} 

编译器误差为'TT': must be a class or namespace when followed by '::',用于ERROR线。

失败的行和唯一不同的行之间的唯一区别是bar_fails通过“模板化typedef”声明“方法指针类型”参数void (TT::*)(),而bar_ok直接声明它。

请注意,如果没有const char*的超载,模板化的typedef可以正常工作。在const char *过载可用的情况下,编译器错误地选择了bar_fails的重载,但它正确地选择了bar_ok的TT = Foo重载。当typedef适用于像TT *或float *这样的“简单”数据时,不会出现此问题。

+0

您标记为'// compiles'的行没有办法。所有这些成员都是“私人”的开始。你能告诉我们真实的代码吗? –

+0

'main'也返回'int' ...(看起来是拼写错误) –

+0

@PierreFourgeaud对不起,我修复了这个问题 – Schollii

回答

2

原因是在bar_ok的情况下,SFINAE可以适用,因为错误的构造const char::*出现在模板参数替换的直接上下文中。在bar_fail的情况下,它被删除了一步(隐藏在“template typedef”中),这意味着SFINAE不再适用,编译器必须处理const char::*的句法废话,从而停止并报告错误。

换句话说,并不是编译器在bar_fail中选择错误的超载。它必须在两种情况下检查两种超载情况,但第一种情况是允许SFINAE忽略错误的情况,而第二种情况是“太迟了”。

+0

我没有注意到“直接上下文”的微妙之处,但经过基于这个答案的进一步测试,这确实是问题。解决方法在下一个答案中,因为我无法将其纳入评论。我将离开Angew的回答作为接受的答案,因为如果没有它,我不知道去哪里寻找。谢谢Angew! – Schollii

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++中完全不可用,则会停止编译。现在

如果你改变了上面的实现略有使用特性,像类,下面的完全等价的代码不再编译:

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

因为编译器是“内部”的Helper<int>类时,它是解决MyFoo typedef;它在int::Foo,直接SFINAE不适用,所以它放弃编译。

你可能注意到了,在我的OP,我并没有明确在上述指定模板参数,而我做的:这是因为编译器知道参数是一个int这样,那需要int作为参数的所有testFunc相匹配,它不会尝试所有的testFunc<int>。在我的OP中,我不需要明确指定模板参数来获取错误,因为第一个函数参数为我做了这个。让我们废除的方法指针,因为它密布的问题,我原来的问题是由以下简单的代码显示在这里我没有明确指定模板参数:

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>里面试图解决const char::Foo并暂停编译(再次指出SFINAE不适用)。

一种解决方法是删除Helper,坚持直接嵌套类型:

struct Foo 
{ 
    template <typename TT> void bar(TT*, typename TT::Foo) {} 
    template <typename TT> void bar(const char*, TT*) {} 
}; 

然而,这是不理想的,因为我在我实际的代码我bar()期待一个方法指针,我想在声明中明确地说明这一点(尽管我现在想知道如果模板化typedef有帮助或阻碍,但那是另一回事)。

第二种解决方案是使用SFINAE在bar级别编译器阻止。原来,这是很容易做到:定义Helper一个专门为const char是缺少的部分:现在,当编译器相匹配TTconst 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*) {} 
}; 

,它有两个重载需要考虑:

void bar(const char*, Helper<const char>::MyFoo) 
void bar(const char*, Foo*) 

Helper<const char>::MyFoo在专业化中不存在,所以可以使用SFINAE:编译器不必去“内部”Helper<T>

这3条专业化线路足以解决这个问题。

+0

很好解释,+1。 – Angew