方法指针模板不编译

时间:2013-08-20 05:11:34

标签: c++ templates typedef

以下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 *或浮动*。

2 个答案:

答案 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条专业化线足以解决问题。