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

Hos*_*ork 3 c++ sfinae type-traits template-meta-programming c++11

我是新手编写模板元编程代码(而不仅仅是阅读它).所以我正在与一些菜鸟问题发生冲突.其中一个很好地总结了这个名为"我的SFINAE发生了什么?"的非SO帖子.,我将C++ 11-ize如下:

(注意:我在这个"思想实验"示例中给出了方法不同的名称,仅用于帮助我的错误诊断.请参阅@ 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;
}
Run Code Online (Sandbox Code Playgroud)

@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;
}
Run Code Online (Sandbox Code Playgroud)

但是如果这没有被破坏(是吗?),那么它肯定不会遵循一个很好的通用方法来解决如何根据嗅探特征的类型来打开和关闭模板化类中的方法.有更好的解决方案吗?

Fle*_*exo 10

首先,C++ 11 没有继续推进disable_if.因此,如果您要转换升级代码,则需要使用enable_if否定条件(或重新定义自己的disable_if构造).

其次,为了使SFINAE达到并应用于方法级别,这些方法必须是模板本身.然而,您的测试必须针对这些模板的参数进行...所以代码enable_if<is_pointer<T>就不起作用了.你可以通过使一些模板参数(比如说X)默认等于T来解决这个问题,然后抛出一个静态断言,调用者没有明确地将它专门化为其他东西.

这意味着不是写作:

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 { /* ... */ }
};
Run Code Online (Sandbox Code Playgroud)

......你会写:

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");
        /* ... */
    }
};
Run Code Online (Sandbox Code Playgroud)

两者现在都是模板,并且enable_if使用模板参数X,而不是整个类的T. 它特别是在为重载决策创建候选集时发生的替换 - 在初始版本中,在重载解析期间没有发生模板替换.

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

Foo<int>().valid_if_pointer<int*>(someInt);
Run Code Online (Sandbox Code Playgroud)

  • 这已破了.`X`需要处于非推导的上下文中,否则它可能与`T`不同.例如,您的代码接受`Foo <int*> x; x.valid_if_not_pointer(0);`. (3认同)
  • 实际上,`static_assert(is_same <X,T> :: value,"不能明确地专门化这个方法");`方法体中的`可能更合适. (2认同)

R. *_*des 5

我认为你不需要SFINAE.SFINAE可用于在不同的模板化过载之间进行选择.基本上,你用它来帮助编译器在template <typename Pointer> void f(Pointer);和之间选择template <typename NotPointer> void f(NotPointer);.

这不是你想要的.在这里,您有两个具有不同名称的函数,而不是两个相同的重载.编译器已经可以在template <typename Pointer> void f(Pointer);和之间选择template <typename NotPointer> void g(NotPointer);.

我举一个例子来解释为什么我认为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
Run Code Online (Sandbox Code Playgroud)

现在,让我们说你设法让这个与SFINAE合作.尝试编译这段代码会在第1行和第4行产生错误.这些错误将是"未找到成员"或类似的错误.它甚至可以将该函数列为过载分辨率中的丢弃候选者.

现在,假设您没有使用SFINAE,而是使用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
    }
};
Run Code Online (Sandbox Code Playgroud)

有了这个,你会在同一条线上得到错误.但是你会得到极短且有用的错误.人们多年来一直在询问编译器编写者.它现在就在你家门口:)

你会得到同样的结果:两种情况都会出错,除非你在没有SFINAE的情况下得到更好的结果.

另请注意,如果您根本不使用static_assert并且函数的实现仅在分别给定指针或非指针时有效,您仍然会在相应的行上出错,除非可能更糟糕.

TL; DR:除非你有两个具有相同名称的实际模板函数,否则最好使用static_assert而不是SFINAE.

  • 原来如此.继续吧:) (3认同)