Reputation: 3655
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
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
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