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?`
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.).
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.
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.
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);
}
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;
}
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;
}
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