例如
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>
强制正确使用 with 。static_assert( sizeof...(Dummy) == 0, "Incorrect usage" );
这两种方法确实使获取成员的地址变得更加困难:它应该看起来像 eg &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
只采用两个模板参数反过来从这样的实现继承。