If you are stuck in c++17, there is some infrastructure that you can add to make detection like this much easier.
Detection-idiom
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
Concepts (C++20+ only)
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