Water
Water

Reputation: 3655

Disabling a template class member function with SFINAE

Suppose I have a class that accepts some type T. This means it could accept some type optional<U>. I want to disable a function if it is not of an optional type, but if it is... then I want to know that type U.

I've been able to to disable the function via templates, but I don't know how to handle detecting a templated template class without writing the same class twice and making one a templated template version.

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();
}

In doing this, I 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() {

                   ^~~~~

Note: It is okay if there is a specialization of this class, all I care about is detecting if its std::optional. I do not need it to work for any other type... only optional. This may allow for some kind of template specialization but I didn't figure out how to do that when researching it.

How can I make this function only appear when the type is std::optional, and then when it is that type, be able to grab the type inside the optional? Can I do this without touching the template definition of T? (as in, can I do it while leaving it as template <typename T> without changing it to template <template <typename> T> or having to copy this class where both the above are made)

Upvotes: 1

Views: 267

Answers (2)

Barry
Barry

Reputation: 302932

Your problem here:

// 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>>>>

Is that you introduced a new dummy template parameter, R, but you're still using the old one, T, for all your checks. So none of the checks are actually dependent. Swap them to R and you're fine.


A different approach would be to defer to a different function using just a tag parameter:

template <typename> struct tag { };

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

Where now you can effectively just use normal template deduction to pull out the type:

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

Upvotes: 2

max66
max66

Reputation: 66200

I suggest to define a custom type traits as follows

template <typename>
struct optionalType
 { };

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

that define type (the type T of the std::optional<T>) when and only when invoked with a std::optional.

Now your stuff() simply become

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

Observe that OptT, the type of the std::optional, is present only when R (aka T) is a std::optional; you have a substitution failure, otherwise, that disable stuff().

You can verify that

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

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

Upvotes: 2

Related Questions