启用非模板成员函数当且仅当它会进行类型检查

m42*_*42a 4 c++ sfinae c++11 c++-templates

例如

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

如果baz无效,我仍然希望程序能够编译,只要没有人真正调用baz.

Luc*_*ton 6

您可以创建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();
};
Run Code Online (Sandbox Code Playgroud)

T参数对于使计算表达式依赖于类型是必需的,否则 SFINAE 不适用。如果您担心此实现细节“泄漏”并且有人可能会尝试f.baz<int>(),您可以在函数体中声明bazwithtemplate<typename... Dummy, typename T = T1>并强制正确使用 eg 。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();
};
Run Code Online (Sandbox Code Playgroud)

在这种情况下,&foo<T, U>::baz获取成员的地址就可以了(当然假设它存在)。两种专业化所共有的代码可以在公共基础中分解出来,并且如果担心作为实现细节引入的附加模板参数可能会泄漏,则可以有一个仅采用foo两个模板参数的“真实”参数依次继承这样的实现。