使用 SFINAE 禁用模板 class 成员函数

Disabling a template class member function with SFINAE

假设我有一个 class 接受某种类型 T。这意味着它可以接受某种类型 optional<U>。我想禁用一个函数,如果它不是 optional 类型,但如果它是......那么我想知道那个类型 U.

我已经能够通过模板禁用该功能,但我不知道如何处理检测模板化模板 class 而不编写相同的 class 两次并制作一个模板化模板版本。

Code:

class Dummy{};

template <typename T>
class C {
    T t;
    
public:
    C(T t) : t(std::move(t)) { }
    
    T get() {
        return t;
    }
    
    // Will clearly fail when T doesn't have a value_type
    template <typename R = T, typename OptT = typename T::value_type, typename = std::enable_if_t<std::is_same_v<T, optional<OptT>>>>
    std::vector<OptT> stuff() {
        std::vector<OptT> vec;
        // Do stuff, fill vec
        return vec;
    }
};

int main() {
    C<Dummy> c{Dummy()};                // Error
    // C<optional<Dummy>> c{Dummy()};   // Works fine
    c.get();
}

在这样做的过程中,我得到了

main.cpp: In instantiation of 'class C':

main.cpp:33:14: required from here

main.cpp:25:23: error: no type named 'value_type' in 'class Dummy'

 std::vector<OptT> stuff() {

                   ^~~~~

注意:如果这个class有专门化也没关系,我只关心检测它是否std::optional。我不需要它为任何其他类型工作......只是可选的。这可能允许某种模板专业化,但我在研究它时没有弄清楚如何做到这一点。

如何让这个函数只在类型为std::optional的时候出现,然后当是那个类型的时候,能够抓取optional里面的类型?我可以在不触及 T 的模板定义的情况下执行此操作吗? (例如,我可以在不将其更改为 template <template <typename> T> 或必须复制此 class 的情况下将其保留为 template <typename T> 来完成上述两项操作)

我建议定义一个自定义类型特征如下

template <typename>
struct optionalType
 { };

template <typename T>
struct optionalType<std::optional<T>>
 { using type = T; };

定义 typestd::optional<T>T 类型)当且仅当使用 std::optional.

调用时

现在你的 stuff() 变成了

template <typename R = T,
          typename OptT = typename optionalType<R>::type>
std::vector<OptT> stuff() {
    std::vector<OptT> vec;
    // Do stuff, fill vec
    return vec;
}

观察 std::optional 的类型 OptT 仅在 R(又名 T)是 std::optional 时出现;你有一个替换失败,否则,禁用 stuff().

你可以验证

C<Dummy>                c0{Dummy()};
C<std::optional<Dummy>> c1{Dummy()};

//c0.stuff(); // compilation error
c1.stuff();   // compile

您的问题:

// Will clearly fail when T doesn't have a value_type
template <typename R = T,
          typename OptT = typename T::value_type,
          typename = std::enable_if_t<std::is_same_v<T, optional<OptT>>>>

您是否引入了一个新的虚拟模板参数 R,但您仍在使用旧参数 T 进行所有检查。所以 none 的支票实际上是依赖的。将它们交换为 R 就可以了。


另一种方法是仅使用标记参数来延迟到不同的函数:

template <typename> struct tag { };

template <typename R=T>
auto stuff() -> decltype(stuff_impl(tag<R>{})) {
    return stuff_impl(tag<R>{});
}

现在你可以有效地使用普通模板推导来提取类型:

template <typename U>
std::vector<U> stuff_impl(tag<std::optional<U>>) {
    return {};
}