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;
}
convert<From, To>
function for types which does not have a trivial conversion operation, i.e. checking forstd::is_convertible
is not sufficient. – Preoccupation