我刚开始编写模板元编程代码(而不仅仅是阅读它)。所以我正在与一些菜鸟问题发生冲突。其中一个很好地总结了这个名为"What happened to my SFINAE?"的非SO帖子,我将C ++ 11-ize作为:
(注意:我在这个“思想实验”示例中给出了方法不同的名称,仅用于帮助我的错误诊断。请参阅@R.MartinhoFernandes's notes为什么你不会在实践中为非实际选择这种方法-overloads。)
#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;
}
但如果没有打破(是吗?),它肯定没有遵循一个很好的通用方法来解决如何根据嗅探特征的类型来打开和关闭模板化类中的方法。有更好的解决方案吗?
答案 0 :(得分:8)
首先,C ++ 11 did not carry forward boost's 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 { /* ... */ }
};
...你会写:
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);
答案 1 :(得分: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
现在,让我们说你设法让这个与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。