According to en.cppreference.com in convertible_to:
The concept convertible_to<From, To> specifies that an expression of the same type and value category as those of std::declval() can be implicitly and explicitly converted to the type To, and the two forms of conversion are equivalent.
I Understood this to mean that convertible_to
concept will identify convertible_to<U, V>
as satisfied if U
has a member explicit operator V()
. Even if the operator is explicit and not an implicit operator.
However I have discovered that this is not the case in msvc.
The following code fails to compile due to the static assert:
#include <concepts>
#include <iostream>
class ExplictClass
{
public:
inline explicit operator int() const
{
return 5;
}
};
int main()
{
static_assert(std::convertible_to<ExplictClass, int>, "Explicit not convertible?"); // Fails here, concept not satisfied.
}
Is this a misunderstanding I have about the concept std::convertible_to
, an error in the code, or error in en.cppreference.com, or Non-conformance of msvc v19.
Attached link to compiler explorer with above code using x64 msvc v19.latest
EDIT:
The actual need for convertible_to
was to generate a to_string
function template that works well with castable types declared as so:
template<class T>
std::string to_string(T val)
{
std::ostringstream stream;
stream << val;
return stream.str();
}
template<Convertible<std::string> T>
std::string to_string(T val)
{
return static_cast<std::string>(val);
}
Here I want types like const char*
, char*
, a type like ExplicitClass
(with explicit operator std::string
) all to use the second method, with the first method being a fallback option
std::convertible_to
requires both implicit and explicit conversion1).
This can be seen in the definition:
template <class From, class To>
concept convertible_to =
std::is_convertible_v<From, To> && // implicit conversion
requires { static_cast<To>(std::declval<From>()); }; // explicit conversion
1) Note: implicit convertibility almost always implies explicit convertibility
Your ExplicitClass
only has an explicit conversion to int
, meaning that you can write ExplicitClass(0)
, but it would not be possible to write ExplicitClass e = 0;
, which requires an implicit conversion.
If you want a concept which tests whether ExplicitClass
can be explicitly converted to int
, you have two options:
std::convertible_to
template <class From, class To>
concept explicitly_convertible_to = requires {
static_cast<To>(std::declval<From>());
};
// assertion passes
static_assert(explicitly_convertible_to<ExplictClass, int>);
// constraint satisfied for T = ExplicitClass
template <explicitly_convertible_to<int> T>
int foo(T t);
std::constructible_from
// assertion passes
static_assert(std::constructible_from<int, From>);
// constraint satisfied for T = ExplicitClass
template <typename T>
requires std::constructible_from<int, T>
int foo(T t);
Note that std::constructible_from<To, From>
will test whether:
std::is_nothrow_destructible_v<To>
, andTo obj(From)
is well-formed, which is a weaker form of static_cast<To>(From)
(both are direct initializaton, but only static_cast
can downcast pointers from Base*
to Derived*
, among other differences)This means that std::constructible_from
isn't equivalent to a static_cast
, but it will do exactly what you want for class types like ExplicitClass
.