I am going down the route
struct S {
static constexpr int extra=5;
};
struct V {
};
template <typename T>
void f()
{
if (std::is_same_v<decltype(T::extra), int>)
std::cout<< "extra exists" <<std::endl;
}
but calling f<S>()
fails as
std::is_same_v<decltype(S::extra), int> == 0
and f<V>()
does not compile
If you are stuck in c++17, there is some infrastructure that you can add to make detection like this much easier.
The most reusable/consistent way to detect features like this is via the Detection idiom, which leverages SFINAE through std::void_t
in a template.
This can be taken verbatim from std::experimental::is_detected
's page from cppreference. This effectively offers C++17 the ability to detect features in a way that is similiar to C++20's concepts; and the infrastructure can be reused easily for just about any detection.
The basics of what you would need are:
#include <type_traits>
namespace detail {
template <class Default, class AlwaysVoid,
template<class...> class Op, class... Args>
struct detector {
using value_t = std::false_type;
using type = Default;
};
template <class Default, template<class...> class Op, class... Args>
struct detector<Default, std::void_t<Op<Args...>>, Op, Args...> {
using value_t = std::true_type;
using type = Op<Args...>;
};
} // namespace detail
struct nonesuch{};
template <template<class...> class Op, class... Args>
using is_detected = typename detail::detector<nonesuch, void, Op, Args...>::value_t;
Note: The above infrastructure can be reused for any detection. It is a very useful reusable tool in C++17.
With is_detected
, all you need is a detector, which is simply a template alias that evaluates to a decltype
expression of something that may, or may not, exist.
So in your case, to conditionally detect the presence of T::extra
, you can do this with a simple detector like:
template <typename T>
using detect_extra = decltype(T::extra);
Putting it all together now, you can use this detector to conditionally toggle the branch:
if constexpr (is_detected<detect_extra,T>::value) {
// Only do code if 'T' has 'T::extra' (e.g. 'S')
} else {
// Only do code if 'T' does not have 'T::extra' (e.g. 'V')
}
Live Example
If equivalent conversion to a specific type is important, such as extra
needing to be convertible to int
, you can also use is_detected_convertible
and use the detector to check for if the result can be convertible to the desired type. Using the same cppreference page again, you can define is_detected_convertible
as:
template <template<class...> class Op, class... Args>
using detected_t = typename detail::detector<nonesuch, void, Op, Args...>::type;
template <class To, template<class...> class Op, class... Args>
using is_detected_convertible = std::is_convertible<detected_t<Op, Args...>, To>;
Which allows the check to instead be:
if constexpr (is_detected_convertible<int, detect_extra, T>::value) {
// Only do code if 'T' has 'T::extra' convertible to int (e.g. 'S')
} else {
// Only do code if 'T' does not have 'T::extra', or is not int
}
Live Example
If you have access to c++20 and beyond, concept
s make this much simpler -- since you can simply use a concept
+ requires
clause like:
#include <concepts> // std::same_as
template <typename T>
concept HasExtra = requires(T) {
{T::extra} -> std::same_as<int>;
};
if constexpr (HasExtra<T>) {
// Only do code if 'T' has 'T::extra' and is 'int' (e.g. 'S')
} else {
// Only do code if 'T' does not have 'T::extra' (e.g. 'V')
}
Live Example
Observe that decltype(T::extra)
(when T
is S
) is int const
(is constexpr
so is also const
), not int
. This explain why f<S>()
fails.
To test if a class has a member variable there are many ways, I suppose.
A possible solution is develop something as
void type_extra (...);
template <typename T>
auto type_extra (T t) -> decltype(t.extra);
template <typename T>
using type_extra_t = decltype(type_extra(std::declval<T>()));
Now you can write f()
as follows
template <typename T>
void f()
{
if ( not std::is_same_v<type_extra_t<T>, void> )
{
std::cout<< "extra exists" <<std::endl;
if ( std::is_same_v<type_extra_t<T>, int> )
std::cout<< "(and is int)" << std::endl;
}
}
Observe that now the type_extra_t<S>
is int
, not int const
; this way (getting the type from the returned type of a function) loose the constness of the variable.
If you want maintain the constness, you can return a reference from the function (so it return a int const &
in the S
case)
template <typename T>
auto type_extra (T t) -> decltype(t.extra) &;
and remove the reference from the using (so, in the S
case, int const &
become int const
)
template <typename T>
using type_extra_t
= std::remove_reference_t<decltype(type_extra(std::declval<T>()))>;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With