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_failsvoid (TT::*)()通过“模板化 typedef”声明“方法指针类型”参数,而 bar_ok 直接声明它。

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

4

2 回答 2

2

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

换句话说,并不是编译器在bar_fail. 它必须在两种情况下检查两种重载,但在第一种情况下,SFINAE 允许忽略错误的情况,而在第二种情况下,它“为时已晚”。

于 2013-08-20T05:19:23.140 回答
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 中,我没有明确指定模板参数,而在上面,我这样做了:这是因为编译器知道 param 是一个 int 所以它匹配所有testFunc作为int参数的参数,它不会尝试所有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视为 a 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

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*)

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

这 3 行专业化足以解决问题。

于 2013-08-20T17:14:19.647 回答