4

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

max66
  • 65,235
  • 10
  • 71
  • 111
MMM
  • 910
  • 1
  • 9
  • 25

2 Answers2

6

If you are stuck in , 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 and beyond, concepts 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

Human-Compiler
  • 11,022
  • 1
  • 32
  • 59
  • Your `C++20` solution does not work because expression concept checks use `decltype((e))` rather than `decltype(e)`. This results in `decltype((T::extra))` being of type `int const&` when feeding in type `S`, which does not satisfy the return-type-requirement of `std::same_as`. – 303 Jan 07 '23 at 19:22
3

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>()))>;
max66
  • 65,235
  • 10
  • 71
  • 111
  • Interesting. Having the same idea, I tried my luck with `std::remove_cv` but this didn't work. Maybe, I oversaw something... [**My try on coliru**](http://coliru.stacked-crooked.com/a/f1cfd7543e9c2ca8) ... while your approach seems to work fine: [**Demo on coliru**](http://coliru.stacked-crooked.com/a/bc844f02a7c19948) – Scheff's Cat Feb 06 '21 at 16:13
  • Was not aware C++ had the alternative token `not`, makes conditional expr so much easier to read – Mansoor Feb 06 '21 at 16:18
  • 1
    @Scheff - You have to use `typename std::remove_cv<...>::type` (or `std::remove_cv_t<...>`), not simply `std::remove_cv<...>` (you have to extract the internal `type`) – max66 Feb 06 '21 at 16:23
  • @M.A - I'm agree: I find very difficult to read a `! condition`. – max66 Feb 06 '21 at 16:25
  • I somehow was expecting hat I oversaw something stupid... Now, it works: [**Demo on coliru**](http://coliru.stacked-crooked.com/a/a62499c3eb0f9695). Thanks for the enlightment. – Scheff's Cat Feb 06 '21 at 16:45
  • @Scheff - Nobody is perfect. I've done your exact error zillions of times. – max66 Feb 06 '21 at 17:08