使用SFINAE原理时,重载功能不明确

时间:2011-02-02 09:04:23

标签: c++ templates sfinae

我遇到了一些用VS7.1编写的代码,现在我正试图让它适用于MacOSX。我理解的代码片段是关于SFINAE原则。根据我的理解,代码用于在编译时通过依赖一些模板实例化魔术知道什么类型的东西。简而言之,通过查看模板参数来选择正确的重载。

这是我的代码。有些简化只能说明问题。

template <typename T>
struct SomeClass
{
};

template <>
struct SomeClass<char>
{
    typedef char Type;
};

template <typename T>
struct IsChar
{
    typedef char Yes;
    typedef int No;

    template <typename U>
    static Yes Select(U*, typename SomeClass<U>::Type* p = 0);
    template <typename U>
    static No Select(U*, ...);
    static T* MakeT();

    const static bool Value = sizeof(Select(MakeT())) == sizeof(Yes);
};

我只是这样使用它:

if (IsChar<int>::Value)
{
    ...

编译上面的代码效果很好时,由于在使用int时缺少Type的typedef,它会选择最顶层的类。

如果我现在使用char而不是......

if (IsChar<char>::Value)
{
    ...

... 编译器会抱怨含糊不清的Select函数,因为它不知道使用哪一个。从我读过的重载决议中,最不喜欢省略号参数(...)。因此,它应该知道选择第一个。

代码在至少VS7.1上工作正常,但不是用于MacOSX的gcc而不是用于Linux的gcc4.4。

有任何建议如何更正?也许它通常以另一种方式完成?

谢谢!

更新:我意识到我给出的示例代码可能略微过分简化,因为我相信我们不会在这里检查类型,即使我错误地让它看起来像那样。今晚我将不得不为你收集更多信息,因为我这里没有代码。对不起。

UPDATE2:即使我的演示文稿很糟糕,也是因为不熟悉原始代码或以这种方式使用模板。同时我挖出了更多的信息,让我们假设这些结构是由于某种原因X和我给出的名字都是错的,编译器问题怎么样?为什么在这里无法选择正确的重载功能?这也很有趣。正如我所说,我会更好地解释总体目标是什么。

修改

在仔细查看原始代码之后,它正在使用boost :: integral_constant以及boost :: enable_if,就像这里建议的那样。问题是特定于模板参数的推导方式,并且它不像设置的那样工作。然而,按照乔治在他的回答结束时提出的建议,我可以纠正接受的事情。我现在有以下内容:

typedef char Yes;
typedef int No;

template <typename U> static Yes Select(typename SomeClass<U>::Type* p);
template <typename U> static No Select(...);

static const bool Value = sizeof(Select<T>(0)) == sizeof(Yes);

这很有效。在尝试一下时,我发现在Select函数中有两个函数参数会导致问题。我没有找到原因。当我更好地理解事情时,我会回到这里。

感谢您的帮助。至少我现在理解这里的原则以及事情应该如何运作。只有一些细节,仍然是未知的。

2 个答案:

答案 0 :(得分:9)

除非我误解了意图,否则上述使用样本不需要使用SFINAE。如果你只想静态断言Type的类型,你可以使用这样的东西:

template<class T1, class T2> struct SameType {
    static const bool Value = false;
};

template<class T> struct SameType<T, T> {
    static const bool Value = true;
};

template <typename T>
struct IsChar {
    static const bool Value = SameType<T, char>::Value;
};

如果你真的需要将它用于SFINAE(即根据模板参数禁用/启用功能),只需将上述内容与Boosts enable_if之类的内容结合使用:

template<class T> 
typename boost::enable_if_c<IsChar<T>::Value, void>::type
someFunction() {
}

或者如果你可以一路狂奔:

template<class T> 
typename boost::enable_if<boost::mpl::is_same<T, char>, void>::type
someFunction() {
}

<强>更新

重读此内容,如果您确实想检查SomeClass的特化是否有typedef Type,那么您应该能够使用over here中的解决方案:

template<class T> struct HasType {
    template<class U> static char (&test(typename U::Type const*))[1];
    template<class U> static char (&test(...))[2];
    static const bool Value = (sizeof(test< SomeClass<T> >(0)) == 1);
};

在这种情况下,IsChar肯定是用词不当,HasTypeHasTypedefType之类的内容会更具描述性:)

答案 1 :(得分:6)

在我的评论中,我说您通常不会将这些谓词的结果用作if的值,这就是原因:

// assume we have has_typedef_type from the Wikipedia page:

template <typename T>
void foo(const T& x)
{
    if (has_typedef_type<T>::value)
    {
        // the predicate is true, so T::type exists
        typename T::type y = x;
    }
    else
    {
        // the predicate is false, so T::type doesn't exist, do something else
        float y = x;
    }
}

这可能看起来很好,但考虑谓词是假的时候,编译器会尝试编译它:

// let's say we called it with double
void foo<double>(const double& x)
{
    if (false)
    {
        // wait, double doesn't have this!
        typename double::type y = x;
    }
    else
    {
        float y = x;
    }
}

问题在于,代码即使在删除死代码的情况下被移除,也是不正确的。解决方案是使if编译时也是如此,但首先是一些样板:

// in C++0x, these are defined in <type_traits>, but we'll do it ourselves
// (Boost has these as well)
typename <typename T, T Value>
struct integral_constant
{
    typedef T type;
    static const T value = Value;
};

typedef integral_constant<bool, true> true_type;
typedef integral_constant<bool, false> false_type;

除此之外,我们定义了我们的功能:

namespace detail
{
    // here are the real implementations
    template <typename T>
    void foo(const T& x, true_type)
    {
        // the predicate is true, so T::type exists
        typename T::type y = x;
    }

    template <typename T>
    void foo(const T& x, false_type)
    {
        // the predicate is false, so T::type doesn't exist, do something else
        float y = x;
    }
}

template <typename T>
void foo(const T& x)
{
    detail::foo(x, // chose which function to call, using the type of this:
                integral_constant<bool, has_typedef_type<T>::value>());
}

现在一切都很好,因为分支完全相互独立。