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
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 f
s 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 f
s 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>;
I can see 2 ways to fix the issue you are seeing:
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>()));
};
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.
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