4

我是编写模板元编程代码的新手(而不是仅仅阅读它)。所以我遇到了一些菜鸟问题。这篇名为“我的 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;
}

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

4

2 回答 2

12

首先,C++11没有继承 boost 的disable_if. 因此,如果您要转换 boost 代码,则需要使用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 { /* ... */ }
};

...你会写:

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);
于 2012-07-17T23:02:05.190 回答
6

在我看来,你不希望 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

现在,假设您设法与 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_assert而不是 SFINAE。

于 2012-07-17T23:34:51.353 回答