例如
template <class T1, class T2>
class foo
{
T1 t1;
T2 t2;
T1 bar(); //Always exists
decltype(t1(t2)) baz(); //Should only exist if t1(t2) is valid
};
如果
baz
无效,我仍然希望程序能够编译,只要没有人真正调用 baz
。
您可以将
baz
制作为模板,这样,如果返回类型无效,则该成员将被 SFINAE-d 淘汰,而不是导致硬错误:
template <class T1, class T2>
class foo
{
T1 t1;
T2 t2;
T1 bar(); //Always exists
template<typename T = T1>
decltype(std::declval<T&>()(t2)) baz();
};
T
参数对于使计算表达式依赖于类型是必需的,否则 SFINAE 不适用。如果您担心此实现细节“泄漏”并且有人可能会尝试 f.baz<int>()
,您可以使用 baz
声明 template<typename... Dummy, typename T = T1>
并强制执行正确的使用,例如函数体中的static_assert( sizeof...(Dummy) == 0, "Incorrect usage" );
。这两种方法确实使获取成员的地址变得更加困难:它应该看起来像例如: &foo<T, U>::baz<>
。
另一种方法是引入类模板专门化:
template<typename...> struct void_ { using type = void; };
template<typename T1, typename T2, typename = void>
class foo {
// version without baz
};
template<typename T1, typename T2>
class foo<T1, T2, typename void_<decltype(std::declval<T1&>()(std::declval<T2>()))>::type> {
decltype(std::declval<T1&>()(std::declval<T2>())) baz();
};
在这种情况下,
&foo<T, U>::baz
可以获取会员的地址(当然假设它存在)。两种专业化所共有的代码可以在一个公共基础中分解出来,如果担心作为实现细节引入的附加模板参数可能会泄漏,则可以有一个“真正的”foo
,只需要两个模板参数依次继承自这样的实现。