Let's say, I have a few different classes:
class constructible_from_float {
public:
constructible_from_float(float);
};
class constructible_from_double {
public:
constructible_from_double(double);
};
class constructible_from_long_double {
public:
constructible_from_long_double(long double);
};
And then I want to do something based on what type are they constructible from (simplified example):
#include <type_traits>
template <typename T>
constexpr size_t foo() {
if constexpr (std::is_constructible<T, float>::value) {
return 1;
} else if constexpr (std::is_constructible<T, double>::value) {
return 2;
} else if constexpr (std::is_constructible<T, long double>::value) {
return 3;
} else
return -1;
}
But the problem is, all of these return 1
:
[[maybe_unused]] auto float_result = foo<constructible_from_float>();
[[maybe_unused]] auto double_result = foo<constructible_from_double>();
[[maybe_unused]] auto long_double_result = foo<constructible_from_long_double>();
I'm aware that the reason for the behavior is implicit conversions between types.
Is there a legit (usable on at the very least three major compilers: msvc
, gcc
and clang
) way to force the compiler to distinguish between these types.
I'm not allowed to to change the classes (constructible_from_float
, etc.) but can do everything else.
Anything provided by the stable versions of the compilers is ok (including c++2a
).
You have to fool the C++ compiler in revealing to you what implicit conversion it wants to use, then use SFINAE to pull the rug from under its feet, and fail to instantiate the template, but SFINAE, so it's not an error.
#include <type_traits>
#include <iostream>
class constructible_from_float {
public:
constructible_from_float(float);
};
class constructible_from_double {
public:
constructible_from_double(double);
};
class constructible_from_long_double {
public:
constructible_from_long_double(long double);
};
template<typename T> class convertible_only_to {
public:
template<typename S, typename=std::enable_if_t<std::is_same_v<T,S>>>
operator S() const
{
return S{};
}
};
template <typename T>
constexpr int foo() {
if constexpr (std::is_constructible<T,
convertible_only_to<float>>::value) {
return 1;
} else
if constexpr (std::is_constructible<T,
convertible_only_to<double>>::value) {
return 2;
} else
if constexpr (std::is_constructible<T,
convertible_only_to<long double>>::value) {
return 3;
} else
return -1;
}
struct not_constructible_from_anything {};
int main()
{
std::cout << foo<constructible_from_float>() << std::endl;
std::cout << foo<constructible_from_double>() << std::endl;
std::cout << foo<constructible_from_long_double>() << std::endl;
std::cout << foo<not_constructible_from_anything>() << std::endl;
return 0;
}