Search code examples
c++c++20c++-conceptscompiler-explorer

std::convertible_to failing to recognize explicitly convertible types


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

compiler explorer

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


Solution

  • 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:

    A - Use just the explicit part of 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);
    

    B: Use 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:

    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.