Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Test if calling f(x) is possible using metaprogramming

The Stroustrup's book provides an example how to answer the question: "is it possible to call f(x) if x is of type X" (the section 28.4.4 "Further examples with Enable_if"). I've tried to reproduce the example but got something wrong and can't understand what.

In my code below, there is a function f(int). I expect that then the result of has_f<int>::value is 1 (true). The actual result is 0 (false).

#include <type_traits>
#include <iostream>

//
// Meta if/then/else specialization
//
struct substitution_failure { };

template<typename T>
struct substitution_succeeded : std::true_type { };

template<>
struct substitution_succeeded<substitution_failure> : std::false_type { };

//
// sfinae to derive the specialization
//
template<typename T>
struct get_f_result {
private:
  template<typename X>
    static auto check(X const& x) -> decltype(f(x));
  static substitution_failure check(...);
public:
  using type = decltype(check(std::declval<T>()));
};

//
// has_f uses the derived specialization
//
template<typename T>
struct has_f : substitution_succeeded<typename get_f_result<T>::type> { };

//
// We will check if this function call be called,
// once with "char*" and once with "int".
//
int f(int i) {
  std::cout << i;
  return i;
}

int main() {
  auto b1{has_f<char*>::value};
  std::cout << "test(char*) gives: " << b1 << std::endl;
  std::cout << "Just to make sure we can call f(int): ";
  f(777);
  std::cout << std::endl;
  auto b2{has_f<int>::value};
  std::cout << "test(int) gives: " << b2 << std::endl;
}

The output:

test(char*) gives: 0
Just to make sure we can call f(int): 777
test(int) gives: 0
like image 643
olpa Avatar asked Aug 02 '16 20:08

olpa


2 Answers

The main problem is that you're making an unqualified call to f here:

template<typename X>
static auto check(X const& x) -> decltype(f(x));

The fs that will be found will be those in scope at the point of definition of check() (none) and those that are found by argument-dependent lookup in the associated namespaces of X. Since X is int, it has no associated namespaces, and you find no fs there either. Since ADL will never work for int, your function has to be visible before get_f_result is defined. Just moving it up solves that problem.


Now, your has_f is overly complicated. There is no reason for the substitution_succeeded machinery. Just have the two check() overloads return the type you want:

template<typename T>
struct has_f {
private:
    template <typename X>
    static auto check(X const& x)
        -> decltype(f(x), std::true_type{});

    static std::false_type check(...);
public:
  using type = decltype(check(std::declval<T>()));
};

And now has_f<T>::type is already either true_type or false_type.


Of course, even this is overly complicated. Checking if an expression is valid is a fairly common operation, so it'd be helpful to simplify it (borrowed from Yakk, similar to std::is_detected):

namespace impl {
    template <template <class...> class, class, class... >
    struct can_apply : std::false_type { };

    template <template <class...> class Z, class... Ts>
    struct can_apply<Z, std::void_t<Z<Ts...>>, Ts...> : std::true_type { };
};

template <template <class... > class Z, class... Ts>
using can_apply = impl::can_apply<Z, void, Ts...>;

This let's you write:

template <class T>
using result_of_f = decltype(f(std::declval<T>()));

template <class T>
using has_f = can_apply<result_of_f, T>;    
like image 89
Barry Avatar answered Nov 04 '22 10:11

Barry


I can see 2 ways to fix the issue you are seeing:

  1. Forward declare your function f. This is required because you are explicitly calling the function by name in the template get_f_result.

int f(int); template<typename T> struct get_f_result { private: template<typename X> static auto check(X const& x) -> decltype(f(x)); static substitution_failure check(...); public: using type = decltype(check(std::declval<T>())); };

  1. The second solution is to make it more generic i.e not just for f(c) but for all function which takes an int:
//
// sfinae to derive the specialization
//
template <typename Func, Func f, typename T>
struct get_f_result {
private:
  template <typename X>
    static auto check(X const& x) -> decltype(f(x));
  static substitution_failure check(...);
public:
  using type = decltype(check(std::declval<T>()));
};

And Call it like:

template <typename T>
struct has_f : substitution_succeeded <typename get_f_result::type> { };

Again here f needs to be known here..but, you can again make it more generic by shifting the responsibility of providing the function at the caller site.

like image 27
Arunmu Avatar answered Nov 04 '22 08:11

Arunmu