草庐IT

c++ - 为什么 SFINAE (enable_if) 从类定义内部工作而不是从外部工作

coder 2024-02-10 原文

过去几个小时我一直在努力解决一个非常奇怪的问题(在用 SFINAE 解决了 5-6 个其他问题之后,因为我是新手)。基本上在下面的代码中,我想让 f() 为所有可能的模板实例化工作,但是 g() 仅在 N == 2:

#include <type_traits>
#include <iostream>

template<typename T, int N>
class A
{
public:
    void f(void);
    void g(void);
};

template<typename T, int N>
inline void A<T, N>::f()
{
    std::cout << "f()\n";
}

template<typename T, int N, typename std::enable_if<N == 2, void>::type* = nullptr>
inline void A<T, N>::g()
{
    std::cout << "g()\n";
}

int main(int argc, char *argv[])
{
    A<float, 2> obj;
    obj.f();
    obj.g();

    return 0;
}

当我尝试编译它时,我收到一个关于有 3 个而不是两个模板参数的错误。然后,经过一些试验,我决定将 g() 的定义移动到 A 本身的定义中,如下所示:

#include <type_traits>
#include <iostream>

template<typename T, int N>
class A
{
public:
    void f(void);

    template<typename t = T, int n = N, typename std::enable_if<N == 2, void>::type* = nullptr>
    void g()
    {
        std::cout << "g()\n";
    }
};

template<typename T, int N>
inline void A<T, N>::f()
{
    std::cout << "f()\n";
}

int main(int argc, char *argv[])
{
    A<float, 2> obj;
    obj.f();
    obj.g();

    return 0;
}

现在,奇迹般地一切正常。但我的问题是为什么?编译器没有看到在类定义中我试图内联一个也依赖于 3 个模板参数的成员函数吗?或者让我们反过来问:如果它在 A 的定义内有效,为什么它在外部不起作用?哪里不一样?不是还有 3 个参数,这比类 A 需要的模板参数多 +1 吗?

另外,为什么它只在我将第三个参数设为非类型参数而不是类型参数时才起作用?请注意,我实际上创建了一个由 enable_if 返回的类型的指针,并为其分配了一个默认值 nullptr,但我发现我不能像我在这里看到的其他 SO 论坛帖子那样将它作为类型参数留在那里。

非常感谢,谢谢!!!

最佳答案

那是因为模板化类中的模板化函数有两组 模板参数集,而不是一组。因此,“正确”的形式是:

template<typename T, int N>
class A
{
public:
    void f(void);

    template<typename std::enable_if<N == 2, void>::type* = nullptr>
    void g(void);
};

template<typename T, int N>                                            // Class template.
template<typename std::enable_if<N == 2, void>::type* /* = nullptr */> // Function template.
inline void A<T, N>::g()
{
    std::cout << "g()\n";
}

查看实际效果 here .

[请注意,这不是实际上正确的,原因在此答案的底部进行了解释。如果 N != 2 就会坏掉.]

如果您愿意,请继续阅读以获取解释。


还在我身边吗?好的。让我们检查每种情况,好吗?

  1. 定义 A<T, N>::g()外面A :

    template<typename T, int N>
    class A
    {
    public:
        void f(void);
        void g(void);
    };
    
    template<typename T, int N, typename std::enable_if<N == 2, void>::type* = nullptr>
    inline void A<T, N>::g()
    {
        std::cout << "g()\n";
    }
    

    在这种情况下,A<T, N>::g()的模板声明与 A 不匹配的模板声明。因此,编译器会发出错误。此外,g()本身不是模板化的,所以模板不能在不改变的情况下拆分为类模板和函数模板 A的定义。

    template<typename T, int N>
    class A
    {
    public:
        void f(void);
    
        // Here...
        template<typename std::enable_if<N == 2, void>::type* = nullptr>
        void g(void);
    };
    
    // And here.
    template<typename T, int N>                                            // Class template.
    template<typename std::enable_if<N == 2, void>::type* /* = nullptr */> // Function template.
    inline void A<T, N>::g()
    {
        std::cout << "g()\n";
    }
    
  2. 定义 A<T, N>::g()里面A :

    template<typename T, int N>
    class A
    {
    public:
        void f(void);
    
        template<typename t = T, int n = N, typename std::enable_if<N == 2, void>::type* = nullptr>
        void g()
        {
            std::cout << "g()\n";
        }
    };
    

    在这种情况下,由于 g()是内联定义的,它隐式具有 A的模板参数,无需手动指定。因此,g()实际上是:

    // ...
        template<typename T, int N>
        template<typename t = T, int n = N, typename std::enable_if<N == 2, void>::type* = nullptr>
        void g()
        {
            std::cout << "g()\n";
        }
    // ...
    

在这两种情况下,对于 g()要拥有自己的模板参数,同时作为模板类的成员,函数模板参数必须与类模板参数分开。否则,函数的类模板将与类不匹配。


既然我们已经介绍过了,我应该指出 SFINAE 只关注立即模板参数。所以,对于 g()将 SFINAE 与 N 一起使用, N需要是它的模板参数;否则,如果您尝试调用,例如 A<float, 3>{}.g(),您将收到错误消息.如有必要,这可以通过中介来完成。

此外,您需要提供 g() 的版本当 N != 2 时可以调用.这是因为 SFINAE 仅在至少存在一个有效版本的函数时才适用;如果没有 g() 的版本可以调用,然后将发出错误并且不会执行 SFINAE。

template<typename T, int N>
class A
{
public:
    void f(void);

    // Note the use of "MyN".
    template<int MyN = N, typename std::enable_if<MyN == 2, void>::type* = nullptr>
    void g(void);

    // Note the "fail condition" overload.
    template<int MyN = N, typename std::enable_if<MyN != 2, void>::type* = nullptr>
    void g(void);
};

template<typename T, int N>
template<int MyN /*= N*/, typename std::enable_if<MyN == 2, void>::type* /* = nullptr */>
inline void A<T, N>::g()
{
    std::cout << "g()\n";
}

template<typename T, int N>
template<int MyN /*= N*/, typename std::enable_if<MyN != 2, void>::type* /* = nullptr */>
inline void A<T, N>::g()
{
    std::cout << "()g\n";
}

如果这样做,我们可以通过让中介来完成繁重的工作来进一步简化事情。

template<typename T, int N>
class A
{
public:
    void f(void);

    template<bool B = (N == 2), typename std::enable_if<B, void>::type* = nullptr>
    void g(void);

    template<bool B = (N == 2), typename std::enable_if<!B, void>::type* = nullptr>
    void g(void);
};

// ...

查看实际效果 here .

关于c++ - 为什么 SFINAE (enable_if) 从类定义内部工作而不是从外部工作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41904099/

有关c++ - 为什么 SFINAE (enable_if) 从类定义内部工作而不是从外部工作的更多相关文章

  1. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  2. ruby-on-rails - 由于 "wkhtmltopdf",PDFKIT 显然无法正常工作 - 2

    我在从html页面生成PDF时遇到问题。我正在使用PDFkit。在安装它的过程中,我注意到我需要wkhtmltopdf。所以我也安装了它。我做了PDFkit的文档所说的一切......现在我在尝试加载PDF时遇到了这个错误。这里是错误:commandfailed:"/usr/local/bin/wkhtmltopdf""--margin-right""0.75in""--page-size""Letter""--margin-top""0.75in""--margin-bottom""0.75in""--encoding""UTF-8""--margin-left""0.75in""-

  3. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  4. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  5. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  6. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  7. ruby - 为什么 4.1%2 使用 Ruby 返回 0.0999999999999996?但是 4.2%2==0.2 - 2

    为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返

  8. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  9. ruby - 无法让 RSpec 工作—— 'require' : cannot load such file - 2

    我花了三天的时间用头撞墙,试图弄清楚为什么简单的“rake”不能通过我的规范文件。如果您遇到这种情况:任何文件夹路径中都不要有空格!。严重地。事实上,从现在开始,您命名的任何内容都没有空格。这是我的控制台输出:(在/Users/*****/Desktop/LearningRuby/learn_ruby)$rake/Users/*******/Desktop/LearningRuby/learn_ruby/00_hello/hello_spec.rb:116:in`require':cannotloadsuchfile--hello(LoadError) 最佳

  10. ruby - ruby 中的 TOPLEVEL_BINDING 是什么? - 2

    它不等于主线程的binding,这个toplevel作用域是什么?此作用域与主线程中的binding有何不同?>ruby-e'putsTOPLEVEL_BINDING===binding'false 最佳答案 事实是,TOPLEVEL_BINDING始终引用Binding的预定义全局实例,而Kernel#binding创建的新实例>Binding每次封装当前执行上下文。在顶层,它们都包含相同的绑定(bind),但它们不是同一个对象,您无法使用==或===测试它们的绑定(bind)相等性。putsTOPLEVEL_BINDINGput

随机推荐