当前位置: 首页 > 知识库问答 >
问题:

“我的SFINAE发生了什么”redux:条件模板类成员?

郭皓
2023-03-14

我是编写模板元编程代码的新手(而不是仅仅阅读它)。所以我遇到了一些新手问题。其中一个很好地总结了这个名为“我的SFINAE发生了什么?”的非SO帖子,我将C 11化为这样:

(注意:我给这些方法起了不同的名字,只是为了帮助我在这个“思想实验”示例中进行错误诊断。请参阅@R. MartinhoFernandes关于为什么您实际上不会在非重载的实践中选择这种方法的注释。)

#include <type_traits>

using namespace std;

template <typename T>
struct Foo {
    typename enable_if<is_pointer<T>::value, void>::type
    valid_if_pointer(T) const { }

    typename disable_if<is_pointer<T>::value, void>::type
    valid_if_not_pointer(T) const { }
};

int main(int argc, char * argv[])
{
    int someInt = 1020;
    Foo<int*>().valid_if_pointer(&someInt);    
    Foo<int>().valid_if_not_pointer(304);

    return 0;
}

@Alf说SFINAE发生的事情是“它一开始就不存在”,并给出了一个编译但模板化函数而不是类的建议。这可能适用于某些情况,但不是所有情况。(例如:我特别尝试编写一个容器,它可以容纳可能可复制或可能不可复制构造的类型,我需要在此基础上打开和关闭方法。)

作为解决办法,我试了一下。。。这似乎是正确的。

#include <type_traits>

using namespace std;

template <typename T>
struct FooPointerBase {
    void valid_if_pointer(T) const { }
};

template <typename T>
struct FooNonPointerBase {
    void valid_if_not_pointer(T) const { }
};

template <typename T>
struct Foo : public conditional<
    is_pointer<T>::value, 
    FooPointerBase<T>,
    FooNonPointerBase<T> >::type {
};

int main(int argc, char * argv[])
{
    int someInt = 1020;
#if DEMONSTRATE_ERROR_CASES
    Foo<int*>().valid_if_not_pointer(&someInt);
    Foo<int>().valid_if_pointer(304);
#else
    Foo<int*>().valid_if_pointer(&someInt);
    Foo<int>().valid_if_not_pointer(304);
#endif
    return 0;
}

但如果这个没有坏(是吗?),它当然没有遵循一个好的通用方法,即如何在模板类中基于对特征类型的嗅探来打开和关闭方法。有更好的解决方案吗?

共有2个答案

双俊人
2023-03-14

在我看来,你不希望这里有SFINAE。SFINAE对于在不同的模板重载之间进行选择很有用。基本上,您使用它来帮助编译器在模板之间进行选择

这不是你想要的。在这里,您有两个名称不同的函数,而不是两个相同的重载。编译器已经可以在模板之间进行选择

我将举一个例子来解释为什么我认为SFINAE不仅没有必要,而且在这里是不可取的。

Foo<int> not_pointer;
Foo<int*> pointer;

not_pointer.valid_if_pointer(); // #1
not_pointer.valid_if_not_pointer(); // #2
pointer.valid_if_pointer(); // #3
pointer.valid_if_not_pointer(); // #4

现在,让我们假设你设法让这个与SFINAE合作。试图编译这段代码将在第1行和第4行产生错误。这些错误将类似于“未找到成员”或类似的错误。它甚至可以在重载解析中将函数列为丢弃的候选函数。

现在,假设您没有使用SFINAE,而是使用static_assert。这样地:

template <typename T>
struct Foo {
    void valid_if_pointer(T) const {
        static_assert(std::is_pointer<T>::value, "valid_if_pointer only works for pointers");
        // blah blah implementation
    }

    void valid_if_not_pointer(T) const {
        static_assert(!std::is_pointer<T>::value, "valid_if_not_pointer only works for non-pointers");
        // blah blah implementation
    }
};

有了这个,你会在同一行出现错误。但你会得到非常简短和有用的错误。这是人们多年来对编译器作者的要求。现在就在你家门口:)

你会得到同样的结果:两种情况下都会出现错误,但如果没有SFINAE,你会得到更好的结果。

还要注意,如果您根本没有使用static_assert,并且函数的实现只有在分别给定指针或非指针时才有效,那么您仍然会在适当的行上收到错误,除了可能更糟糕的行。

TL;DR:除非您有两个同名的实际模板函数,否则最好使用static\u assert而不是SFINAE。

宋宏儒
2023-03-14

首先,C 11没有将boost的禁用_if结转。因此,如果你要转换boost代码,你需要使用一个否定的条件(或者重新定义你自己的disable_if构造)来使用enable_if

其次,为了使SFINAE达到并应用于方法层面,这些方法本身必须是模板。然而,你的测试必须针对这些模板的参数进行。。。所以像enable_if这样的代码

这意味着代替写作:

template <typename T>
struct Foo {
    typename enable_if<is_pointer<T>::value, void>::type
    valid_if_pointer(T) const { /* ... */ }

    typename disable_if<is_pointer<T>::value, void>::type
    valid_if_not_pointer(T) const { /* ... */ }
};

...你会写:

template <typename T>
struct Foo {
    template <typename X=T>
    typename enable_if<is_pointer<X>::value, void>::type
    valid_if_pointer(T) const {
        static_assert(is_same<X,T>::value, "can't explicitly specialize");
        /* ... */
    }

    template <typename X=T>    
    typename enable_if<not is_pointer<X>::value, void>::type
    valid_if_not_pointer(T) const {
        static_assert(is_same<X,T>::value, "can't explicitly specialize");
        /* ... */
    }
};

现在两者都是模板,enable_if使用模板参数X,而不是用于整个类的T。特别是在创建重载解析候选集时发生的替换——在初始版本中,重载解析期间不会发生模板替换。

请注意,静态断言用于保留原始问题的意图,并防止有人能够编译以下内容:

Foo<int>().valid_if_pointer<int*>(someInt);

 类似资料:
  • 我继承了一个Java应用程序,它被配置为在Google app Engine中运行。我的pom包括com.google.appengine.appengine-maven-plugin插件,它可能与这个问题相关,也可能与这个问题无关。 在我的src目录中,在WEB-INF目录中,我有一个“app.yaml”文件。但是当我的项目构建到一个war中时,目标目录同时有一个“app.yaml”文件和一个“

  • 对于将SFINAE与可变模板类一起使用,我似乎找不到一个好的解决方案。 假设我有一个不喜欢引用的可变参数模板对象: 以及一个类,可以方便地检查参数包是否包含引用: 我如何使用它来专门化NoRef的情况下,引用存在于arg包?

  • 我一直试图理解C++选择模板的方式。即,考虑以下代码示例: 前两个函数(test1)工作正常(为什么?): 一个常见的错误是声明两个仅在默认模板参数上不同的函数模板。这是非法的,因为默认模板参数不是函数模板签名的一部分,并且用相同的签名声明两个不同的函数模板是非法的。 所以看起来是这样的。但是,我看不出与前两个函数有太大的不同,前两个函数也有默认的模板参数。因此,我们对默认值(test1-work

  • 问题内容: 我有一个主程序调用的函数: 但是在执行函数的中间会引发异常,因此它跳到了该部分。 我如何才能准确看到导致异常发生的原因? 问题答案: 其他答案都指出,您不应捕获通用异常,但是似乎没人愿意告诉您原因,这对于理解何时可以打破“规则”至关重要。这)是一个解释。基本上是这样,您不会隐藏: 发生错误的事实 发生的错误的详细信息(错误隐藏反模式) 因此,只要您不做任何事情,就可以捕获通用异常。例如

  • 我有下面的代码,我希望TestEnableIf与不同的专门化有不同的打印功能,但它没有按计划工作,错误如下。 我不明白的是,sfinae应该意味着专门化失败不是错误,那么编译器为什么要抱怨失败?

  • 问题内容: 早期的javadoc 这样表示有一个接口,它似乎有一个同样的关系作为必须的。 现在看来,我们固守在,这肯定是不一样的。 发生了什么事? 问题答案: 它已被删除前一段时间。布赖恩·格茨(Brian Goetz)提出了撤职的理由: 当前,唯一的实现者是Collection,所有其他支持流的方法都使用一种比“ stream”更合适的方法名称来提供特殊的流(chars(),codePoints