There are many similar-looking questions, but I can't find quite this one: Given
template <typename T>
struct S {
int f(int x) const { return x; }
};
I want to be able to SFINAE-away f
depending on some metafunction of T
. The obvious thing doesn't work:
#include <type_traits>
template <typename T>
struct MyProperty { static constexpr auto value = std::is_same_v<T, float>; };
template <typename T>
struct S {
template <typename = std::enable_if_t<MyProperty<T>::value>>
int f(int x) const { return x; }
};
int main() {
S<int> s;
}
The closest I can come is a dummy typename U = T
:
template <typename U = T, typename = std::enable_if_t<MyProperty<U>::value>>
int f(int x) const {
static_assert(std::is_same_v<T, U>, "Don't provide U.");
return x;
}
which works https://godbolt.org/z/8qGs6P8Wd but feels round-about. Is there a better way?
If you can use C++20 or later, add a constraint:
template<class T>
struct S {
int f(int x) const requires MyProperty<T>::value {
return x;
}
};
In C++17, you could do it like you do it now or move the enable_if
to be used for the function's return type. If you put the condition std::is_same_v<U, T>
in the enable_if
too instead of in a static_assert
, it'll be more SFINAE friendly in case you want to enable other f
s.
template<class T>
struct S {
template<class U = T>
std::enable_if_t<std::is_same_v<U, T> && MyProperty<U>::value, int>
f(int x) const {
return x;
}
};
Another option that can be useful if you have many functions in your class that you'd like to enable only if MyProperty::value
is true
is to put all those functions in a base class and inherit from that class conditionally using CRTP.
struct empty {};
template<class T>
struct float_funcs {
T& Self() { return *static_cast<T*>(this); }
const T& Self() const { return *static_cast<const T*>(this); }
// put all functions depending on the MyProperty trait being true here:
int f(int x) const {
return x + Self().foo; // `foo` is accessible since we're a friend
}
};
template<class T> // added helper variable
inline constexpr bool MyProperty_v = MyProperty<T>::value;
// inherit from float_funcs<S<T>> if the condition is `true` or `empty` otherwise
template<class T>
struct S : std::conditional_t<MyProperty_v<T>, float_funcs<S<T>>, empty> {
private:
friend std::conditional_t<MyProperty_v<T>, float_funcs<S<T>>, empty>;
int foo = 1;
};
With this you don't need SFINAE away f
and you'll get a clear compilation error if you try to use f
when MyProperty<T>
is not fulfilled. f
doesn't even exist in any form in that case.