使用SFINAE原则时重载函数是不明确的

发布于 2024-10-15 09:53:48 字数 2083 浏览 4 评论 0原文

我遇到了一些用 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 函数中有两个函数参数会导致问题。我还没找到原因当我更好地理解事情时,我会回到这个话题。

感谢您的帮助。至少我现在了解了这里的原理以及事情应该如何运作。只是一些细节,目前还不得而知。

I came across some code written in VS7.1 and now I'm trying to get it to work for MacOSX. The code snippet below I understand is about the SFINAE principle. From what I understand, the code is used to at compile-time know what type something is by relying on some template instantiation magic. In short, the right overload is picked by looking at the template argument.

Here's the code I have. Somewhat simplified to only show the problem.

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

I'm simply using this like this:

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

When compiling the above code works well and it picks the topmost class due to the missing typedef for Type when using int.

If I now use char instead...

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

...the compiler will complain about ambiguous Select functions, because it doesn't know which one to use. From what I've read overload resolution gives least preference to the ellipsis parameter (...). Thus, it should know to select the first one.

The code was working fine on at least VS7.1, but not on gcc for MacOSX and not gcc4.4 for Linux.

Any suggestions how to correct this? Maybe it's usually done in another way?

Thanks!

UPDATE: I realized that the sample code I gave is maybe slightly too much simplified, because I believe we're not jsut checking for type here even if I mistakenly make it look like that. I'll have to gather a bit more information for you tonight as I don't have the code here. Sorry for that.

UPDATE2: Even if my presenation in bad and it's due to not being familiar with the original code or using templates this way. Meanwhile I dig out a bit more information, let's assume these constructs are there for some reason X and the names I have given are all wrong, what about the compiler problem? Why is it not able to select the right overloaded function here? This is also interesting me. As I said, I'll get back with a better explanation what the overall aim is.

Edit

After taking closer look at the original code, it is using boost::integral_constant and also boost::enable_if like what was suggested here. The problem is something specific to how the template arguments are deduced and it didn't work the way it was set up. However, following what Georg suggested in the end of his answer I could correct things to accept things. I have now the following:

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

This works well. While experimenting a bit I found out that having two function parameters in the Select functions results in a problem. I haven't found the reason. I'll come back to this when I understand things better.

Thanks for all your help. At least I understand the principles here now and how things should work. Only some details, which are still unknown.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

清晰传感 2024-10-22 09:53:48

除非我误解了意图,否则上述使用示例不需要使用 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() {
}

或者如果你可以一路Boost:

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

更新

重读这篇文章,如果你真的想检查 SomeClass 的特化是否有 typedef < code>Type,您应该能够使用 在这里

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 会更具描述性:)

Unless i'm misunderstanding the intent, the above usage sample doesn't require use of SFINAE. If you only want to statically assert the type of Type you can just use something like this:

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

If you really need to use this for SFINAE (i.e. disabling/enabling functions based on template parameters), just use the above in combination with something like Boosts enable_if:

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

Or if you can go Boost all the way:

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

Update:

Rereading this, if you actually wanted to check wether a specialization of SomeClass has a typedef Type, you should be able to use the solution from 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);
};

In that case IsChar is certainly a misnomer though, something like HasType or HasTypedefType would be more descriptive :)

揪着可爱 2024-10-22 09:53:48

在我的评论中,我说过您通常不会使用这些谓词的结果作为 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;
    }
}

这可能看起来不错,但请考虑当谓词为 false 时,编译器将尝试编译此代码:

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

现在一切都很好,因为分支是完全独立的来自彼此。

In my comment I said that you don't typically use the results of these predicates as values for an if, and this is why:

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

This may look fine, but consider when the predicate is false, the compiler is going to try and compile this:

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

The problem is that the code, even though it'll be removed with dead-code removal, is ill-formed. The solution is to make the if compile-time as well, but first some boiler-plate:

// 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;

With that out of the way, we define our functions:

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

Now everything is okay, because the branches are completely independent from one another.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文