I wrote such code:
template <class T>
class A {
template <class U, class =
class std::enable_if_t<std::is_convertible_v<std::decay_t<U>, std::decay_t<T>>>>
void f(U&& val) {}
};
I want that user of my class can call f
only with types that convertible to T
.
Is std::decay
redundant there? If I remove it maybe I miss some special cases?
I think your question is more philosophical, as in: In the universe of types in C++, are there any cases of T and U where there is an observable difference between calling f() and g() on the following class:
template <class T>
struct A {
template <
class U,
enable_if_t<is_convertible_v<decay_t<U>, decay_t<T>>>* = nullptr
>
void f(U&& val) {}
template <
class U,
enable_if_t<is_convertible_v<U, T>>* = nullptr
>
void g(U&& val) {}
};
What does decay_t actually do?
It may be worth noting: decay_t is modeled on what happens to function argument types when passed into a function.
(UPDATED)
If U
is taken by value, then decay_t<U>
will be equivalent to U.
But since U
is a universal reference (in your example), it is possible that U deduces to reference type, so decaying it will have a visible effect. When T
can only be constructed from a reference, then decaying it will change the answer of this type trait.
Merry's excellent example:
#include <type_traits>
#include <utility>
template <class T>
struct A {
template <
class U,
std::enable_if_t<std::is_convertible_v<std::decay_t<U>, T>>* = nullptr
>
void f(U&& val) {}
template <
class U,
std::enable_if_t<std::is_convertible_v<U, T>>* = nullptr
>
void g(U&& val) {}
};
struct B
{
B(int &) {}
};
void foo() {}
int main() {
A<B> a1;
int x = 3;
// a1.f(x); // fails to compile: int not convertible to B
a1.g(x); // ok; int& is convertible to B
}