Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to detect if a function exists?

I'm trying to detect if a specific overload for my function is callable. I assumed I could do something similar to this answer, but I believe the issue is that the function signature template<typename From, typename To> convert(const From&) is well defined, but the instantiation is not.

#include <iostream>
#include <string>

template<typename From, typename To>
To convert(const From& from)
{
    // I have a lot of additional template specializations for this function
    return from; 
}

template<typename From, typename To>
struct IsConvertible
{
    template<typename = decltype(convert<From, To>(From()))>
    static std::true_type test(int);
    template<typename T>
    static std::false_type test(...);

    static bool const value = decltype(test(0))::value;
};

int main()
{
    std::cout << "IsConvertible=" << IsConvertible<int, float>::value << std::endl;
    // Returns 1 as expected

    std::cout << "IsConvertible=" << IsConvertible<int, std::string>::value << std::endl;
    // Returns 1, expected 0. The issue seems to be that decltype(convert<From, To>(From()))
    // is somehow ok, although convert<int, std::string>(1) definitly isn't
}

I want to use IsConvertible for some additional metaprogramming. Is it possible to detect if the template<typename From, typename To> To convert(const From&) function is actually callable?`

like image 693
pingul Avatar asked May 10 '19 06:05

pingul


People also ask

How do you determine whether a function exists by using the type of operator?

The typeof operator gets the data type of the unevaluated operand which can be a literal or a data structure like an object, a function, or a variable. The typeof returns a string with the name of the variable type as a first parameter (object, boolean, undefined, etc.).

How do you know if a function exists?

Use an if Conditional Statement The code in the brackets will execute if the function is defined. If instead you just test the function without using the window object, for example as if(aFunctionName) , JavaScript will throw a ReferenceErorr if the function doesn't exist.

How do you know if a function is true or false?

Determining whether a relation is a function on a graph is relatively easy by using the vertical line test. If a vertical line crosses the relation on the graph only once in all locations, the relation is a function. However, if a vertical line crosses the relation more than once, the relation is not a function.


3 Answers

With declaration of

template<typename From, typename To> To convert(const From& from);

Your traits

template<typename From, typename To>
struct IsConvertible

would always detect presence of convert function.

One way to fix it is overloads and/or SFINAE:

template <typename> struct Tag{};

int convertImpl(tag<int>, const std::string& from);
float convertImpl(tag<float>, const std::string& from);
// overloads ...

template<typename From, typename To>
auto convert(const From& from)
-> decltype(convertImpl(tag<To>{}, from))
{
    return convertImpl(tag<To>{}, from);
}
like image 177
Jarod42 Avatar answered Oct 25 '22 04:10

Jarod42


I might have misunderstood your question but is not the using std::is_invocable enough in this case as showcased in the following?

#include<type_traits>

template<typename From, typename To>
To convert(const From& from)
{
    // I have a lot of additional template specializations for this function
    return from; 
}

template<>
std::string convert(const int& from)
{
    //silly specialization
    return "2"+from; 
}


struct Foo{
    int bar;
};

int main()
{
   //ok specialization is called 
   std::cout<<std::is_invocable<decltype(convert<int,std::string>),std::string>::value<<std::endl; 
   //no way I can convert int to Foo, specialization required
   std::cout<<std::is_invocable<decltype(convert<int,Foo>),Foo>::value<<std::endl; 
return 0;
}
like image 29
Davide Spataro Avatar answered Oct 25 '22 04:10

Davide Spataro


I see some problems in your code.

Without a particular order...

(1) SFINAE, using decltype(), check only the presence of a declared function; doesn't check if that function is defined or if it's definition works (compile) or not.

I propose you to rewrite convert() using directly SFINAE to declare it only when is compilable

template <typename To, typename From,
          decltype( To(std::declval<From>()), bool{} ) = true>
To convert (From const & f)
 { return f; }

This way convert() is declared only if you can construct a To object starting from a From object.

(2) Observe that I've also switched the order of To and From: this way you can call the convert() function explicating only the To type

convert<float>(0); // From is deduced as int from the 0 value

If you declare To (that isn't deducible) after From (that is deducible), you necessarily have to explicit both types, calling the function, also when the From type is deducible.

(3) Your IsConvertible struct doesn't works.

It's a common error using SFINAE.

When you write

template<typename = decltype(convert<From, To>(From()))>
static std::true_type test(int);

you're trying enable/disable this test() method using SFINAE over From and To that are the template parameters of the struct

Wrong.

SFINAE works over template parameters of the method itself.

If you want to use SFINAE, you have to transform From and To in template parameters of the method; by example

template <typename F = From, typename T = To,
          typename = decltype(convert<F, T>(std::declval<F>()))>
static std::true_type test(int);

Now SFINAE uses F and T that are template parameters of the test() method and this is correct.

(4) Observe that I've written std::declval<F>() instead of F(). It's because you're not sure that F (From) is default constructible. With std::declval() you go around this problem.

I propose a different IsConvertible custom type traits that take in count the From/To inversion and demand to the value call of test() the From+To->F+T type conversion

template <typename To, typename From>
struct IsConvertible
 {
   template <typename T, typename F,
             typename = decltype(convert<T>(std::declval<F>()))>
   static std::true_type test(int);

   template <typename...>
   static std::false_type test(...);

   static bool const value = decltype(test<To, From>(0))::value;
 };

(5) you're expecting that

IsConvertible<int, std::string>::value

is zero; but you forgetting that std::string is constructible from int; so this value (or IsConvertible<std::string, int>, switching To and From) should be one.

The following is a corrected full working example

#include <iostream>
#include <string>
#include <vector>

template <typename To, typename From,
          decltype( To(std::declval<From>()), bool{} ) = true>
To convert (From const & f)
 { return f; }

template <typename To, typename From>
struct IsConvertible
 {
   template <typename T, typename F,
             typename = decltype(convert<T>(std::declval<F>()))>
   static std::true_type test(int);

   template <typename...>
   static std::false_type test(...);

   static bool const value = decltype(test<To, From>(0))::value;
 };

int main ()
 {
   std::cout << "IsConvertible=" << IsConvertible<float, int>::value
      << std::endl;

   std::cout << "IsConvertible=" << IsConvertible<int, std::string>::value
      << std::endl;
 }
like image 28
max66 Avatar answered Oct 25 '22 04:10

max66