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

时间:2019-02-10 20:31:49

标签: c++ templates c++17 sfinae

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

我已经能够通过模板禁用该功能,但我不知道如何在不编写两次相同的类并将一个模板化为模板版本的情况下检测模板化的模板类。

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:在“类C”的实例中:

     

main.cpp:33:14:从此处要求

     

main.cpp:25:23:错误:“类虚拟”中没有名为“ value_type”的类型

 std::vector<OptT> stuff() {

                   ^~~~~

注意:如果有该类的专业化,这是可以的,我所关心的只是检测其std::optional。我不需要它为任何其他类型工作……只是可选的。这可能允许某种形式的模板专业化,但是我在研究模板时并没有弄清楚该怎么做。

如何使该函数仅在类型为std::optional时出现,然后才是该类型,才能在可选控件中获取该类型?我可以不触碰T的模板定义吗? (例如,我可以将其保留为template <typename T>而不将其更改为template <template <typename> T>还是不必复制同时完成以上两项的此类)吗?

2 个答案:

答案 0 :(得分:2)

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

template <typename>
struct optionalType
 { };

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

定义{{1}时{{1}的类型type({{1}的类型T),并且仅在使用std::optional<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; } OptT的类型)仅在std::optional(又名R)为T时出现;您的替换失败,否则,将禁用std::optional

您可以验证

stuff()

答案 1 :(得分:2)

您的问题在这里:

// 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进行所有检查。因此,所有检查实际上都不依赖。将它们交换到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 {};
}